change way to describe fields of CapBaseObject, and lot of documentation
This commit is contained in:
parent
99391a95ef
commit
c6a141595c
35 changed files with 1630 additions and 638 deletions
|
|
@ -1,5 +0,0 @@
|
|||
Applications
|
||||
============
|
||||
|
||||
boobank
|
||||
-------
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
Backends
|
||||
========
|
||||
|
||||
cragr
|
||||
-----
|
||||
|
|
@ -10,8 +10,7 @@ def genapi():
|
|||
os.chdir('api')
|
||||
for root, dirs, files in os.walk('../../../weboob/'):
|
||||
root = root.split('/', 4)[-1]
|
||||
if root.startswith('applications') or \
|
||||
root.startswith('backends'):
|
||||
if root.startswith('applications'):
|
||||
continue
|
||||
|
||||
if root.strip():
|
||||
|
|
@ -26,7 +25,7 @@ def genapi():
|
|||
continue
|
||||
|
||||
f, ext = f.rsplit('.', 1)
|
||||
if ext == 'pyc' or f == '__init__':
|
||||
if ext != 'py' or f == '__init__':
|
||||
continue
|
||||
|
||||
subs.add(f)
|
||||
|
|
|
|||
|
|
@ -3,18 +3,12 @@ Weboob
|
|||
|
||||
This is the developer documentation.
|
||||
|
||||
.. warning::
|
||||
This documentation is being written.
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
overview
|
||||
install
|
||||
backends
|
||||
applications
|
||||
guides/index
|
||||
api/index
|
||||
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
Installation
|
||||
============
|
||||
|
||||
If you install Weboob from sources, you'll want to install the Python egg in development mode.
|
||||
You'll be able to update the git repository with remote changes, without re-installing the software.
|
||||
And if you plan to hack on Weboob, you'll see your own changes apply the same way.
|
||||
|
||||
Install
|
||||
-------
|
||||
|
||||
As root:
|
||||
|
||||
``# ./setup.py develop``
|
||||
|
||||
The development mode installation doesn't copies files, but creates an egg-link
|
||||
in the Python system packages directory:
|
||||
|
||||
* ``/usr/lib/python2.5/site-packages`` for Python 2.5
|
||||
* ``/usr/local/lib/python2.6/dist-packages`` for Python 2.6
|
||||
|
||||
Scripts are copied to:
|
||||
|
||||
* ``/usr/bin`` for Python 2.5
|
||||
* ``/usr/local/bin`` for Python 2.6
|
||||
|
||||
Uninstall
|
||||
---------
|
||||
|
||||
* remove the ``/usr/local/lib/python2.6/dist-packages/weboob.egg-link``
|
||||
* remove the weboob line from ``/usr/local/lib/python2.6/dist-packages/easy-install.pth``
|
||||
|
|
@ -3,21 +3,13 @@ Overview
|
|||
|
||||
Weboob (Web Out Of Browsers) provides:
|
||||
|
||||
* :doc:`applications` to interact with websites
|
||||
* :doc:`backends`, each one handles a specific website
|
||||
* :doc:`applications <api/tools/application/index>` to interact with websites
|
||||
* :doc:`backends <api/tools/backend>`, each one handles a specific website
|
||||
* a :doc:`core library <api/core/index>` providing all the features needed by backends
|
||||
* :doc:`tools <api/tools/index>` to help develop backends and applications
|
||||
|
||||
Weboob is written in Python and is distributed under the AGPLv3+ license.
|
||||
|
||||
Why using Weboob?
|
||||
-----------------
|
||||
|
||||
* you get essential information from websites faster
|
||||
* you can write scripts using weboob to automate tasks
|
||||
* you can extend websites features
|
||||
* it helps blind people using crappy websites
|
||||
|
||||
Architecture
|
||||
------------
|
||||
|
||||
|
|
|
|||
|
|
@ -18,23 +18,32 @@
|
|||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from .base import IBaseCap, CapBaseObject
|
||||
from .base import IBaseCap, CapBaseObject, StringField, Field
|
||||
|
||||
|
||||
__all__ = ['ICapAccount']
|
||||
__all__ = ['AccountRegisterError', 'Account', 'StatusField', 'ICapAccount']
|
||||
|
||||
|
||||
class AccountRegisterError(Exception):
|
||||
pass
|
||||
"""
|
||||
Raised when there is an error during registration.
|
||||
"""
|
||||
|
||||
class Account(CapBaseObject):
|
||||
"""
|
||||
Describe an account and its properties.
|
||||
"""
|
||||
login = StringField('Login')
|
||||
password = StringField('Password')
|
||||
properties = Field('List of key/value properties', dict)
|
||||
|
||||
def __init__(self, id=None):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('login', basestring)
|
||||
self.add_field('password', basestring)
|
||||
self.add_field('properties', dict)
|
||||
|
||||
class StatusField(object):
|
||||
"""
|
||||
Field of an account status.
|
||||
"""
|
||||
FIELD_TEXT = 0x001 # the value is a long text
|
||||
FIELD_HTML = 0x002 # the value is HTML formated
|
||||
|
||||
|
|
@ -46,8 +55,14 @@ class StatusField(object):
|
|||
|
||||
|
||||
class ICapAccount(IBaseCap):
|
||||
# This class constant may be a list of Value* objects. If the value remains
|
||||
# None, weboob considers that register_account() isn't supported.
|
||||
"""
|
||||
Capability for websites when you can create and manage accounts.
|
||||
|
||||
:var ACCOUNT_REGISTER_PROPERTIES: This class constant may be a list of
|
||||
:class:`weboob.tools.value.Value` objects.
|
||||
If the value remains None, weboob considers
|
||||
that :func:`register_account` isn't supported.
|
||||
"""
|
||||
ACCOUNT_REGISTER_PROPERTIES = None
|
||||
|
||||
@staticmethod
|
||||
|
|
@ -58,7 +73,9 @@ class ICapAccount(IBaseCap):
|
|||
This is a static method, it would be called even if the backend is
|
||||
instancied.
|
||||
|
||||
@param account an Account object which describe the account to create
|
||||
:param account: describe the account to create
|
||||
:type account: :class:`Account`
|
||||
:raises: :class:`AccountRegisterError`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -84,6 +101,6 @@ class ICapAccount(IBaseCap):
|
|||
"""
|
||||
Get status of the current account.
|
||||
|
||||
@return a list of fields
|
||||
:returns: a list of fields
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
|
|
@ -18,30 +18,45 @@
|
|||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from datetime import datetime, date
|
||||
from datetime import date, datetime
|
||||
|
||||
from .base import CapBaseObject
|
||||
from .base import CapBaseObject, Field, StringField, DateField, FloatField, IntField
|
||||
from .collection import ICapCollection
|
||||
|
||||
|
||||
__all__ = ['Account', 'AccountNotFound', 'TransferError', 'ICapBank', 'Transaction']
|
||||
__all__ = ['AccountNotFound', 'TransferError', 'Recipient', 'Account', 'Transaction', 'Transfer', 'ICapBank']
|
||||
|
||||
|
||||
class AccountNotFound(Exception):
|
||||
def __init__(self, msg=None):
|
||||
if msg is None:
|
||||
msg = 'Account not found'
|
||||
"""
|
||||
Raised when an account is not found.
|
||||
"""
|
||||
|
||||
def __init__(self, msg='Account not found'):
|
||||
Exception.__init__(self, msg)
|
||||
|
||||
class TransferError(Exception):
|
||||
pass
|
||||
"""
|
||||
A transfer has failed.
|
||||
"""
|
||||
|
||||
class Recipient(CapBaseObject):
|
||||
"""
|
||||
Recipient of a transfer.
|
||||
"""
|
||||
|
||||
label = StringField('Name')
|
||||
|
||||
def __init__(self):
|
||||
CapBaseObject.__init__(self, 0)
|
||||
self.add_field('label', basestring)
|
||||
|
||||
class Account(Recipient):
|
||||
"""
|
||||
Bank account.
|
||||
|
||||
It is a child class of :class:`Recipient`, because an account can be
|
||||
a recipient of a transfer.
|
||||
"""
|
||||
TYPE_UNKNOWN = 0
|
||||
TYPE_CHECKING = 1 # Transaction, everyday transactions
|
||||
TYPE_SAVINGS = 2 # Savings/Deposit, can be used for everyday banking
|
||||
|
|
@ -50,17 +65,18 @@ class Account(Recipient):
|
|||
TYPE_MARKET = 5 # Stock market or other variable investments
|
||||
TYPE_JOINT = 6 # Joint account
|
||||
|
||||
def __init__(self):
|
||||
Recipient.__init__(self)
|
||||
self.add_field('type', int, self.TYPE_UNKNOWN)
|
||||
self.add_field('balance', float)
|
||||
self.add_field('coming', float)
|
||||
type = IntField('Type of account', default=TYPE_UNKNOWN)
|
||||
balance = FloatField('Balance on this bank account')
|
||||
coming = FloatField('Coming balance')
|
||||
|
||||
def __repr__(self):
|
||||
return u"<Account id=%r label=%r>" % (self.id, self.label)
|
||||
|
||||
|
||||
class Transaction(CapBaseObject):
|
||||
"""
|
||||
Bank transaction.
|
||||
"""
|
||||
TYPE_UNKNOWN = 0
|
||||
TYPE_TRANSFER = 1
|
||||
TYPE_ORDER = 2
|
||||
|
|
@ -72,47 +88,77 @@ class Transaction(CapBaseObject):
|
|||
TYPE_LOAN_PAYMENT = 8
|
||||
TYPE_BANK = 9
|
||||
|
||||
def __init__(self, id):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('date', (basestring, datetime, date)) # debit date
|
||||
self.add_field('rdate', (datetime, date)) # real date, when the payment has been made
|
||||
self.add_field('type', int, self.TYPE_UNKNOWN)
|
||||
self.add_field('raw', unicode)
|
||||
self.add_field('category', unicode)
|
||||
self.add_field('label', unicode)
|
||||
self.add_field('amount', float)
|
||||
date = DateField('Debit date')
|
||||
rdate = DateField('Real date, when the payment has been made')
|
||||
type = IntField('Type of transaction, use TYPE_* constants', default=TYPE_UNKNOWN)
|
||||
raw = StringField('Raw label of the transaction')
|
||||
category = StringField('Category of transaction')
|
||||
label = StringField('Pretty label')
|
||||
amount = FloatField('Amount of transaction')
|
||||
|
||||
def __repr__(self):
|
||||
return "<Transaction date='%s' label='%s' amount=%s>" % (self.date,
|
||||
self.label, self.amount)
|
||||
|
||||
class Transfer(CapBaseObject):
|
||||
def __init__(self, id):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('amount', float)
|
||||
self.add_field('date', (basestring, datetime, date))
|
||||
self.add_field('origin', (int, long, basestring))
|
||||
self.add_field('recipient', (int, long, basestring))
|
||||
"""
|
||||
Transfer from an account to a recipient.
|
||||
"""
|
||||
|
||||
amount = FloatField('Amount to transfer')
|
||||
date = Field('Date of transfer', basestring, date, datetime)
|
||||
origin = Field('Origin of transfer', int, long, basestring)
|
||||
recipient = Field('Recipient', int, long, basestring)
|
||||
|
||||
class ICapBank(ICapCollection):
|
||||
"""
|
||||
Capability of bank websites to see accounts and transactions.
|
||||
"""
|
||||
def iter_resources(self, objs, split_path):
|
||||
"""
|
||||
Iter resources.
|
||||
|
||||
Default implementation of this method is to return on top-level
|
||||
all accounts (by calling :func:`iter_accounts`).
|
||||
|
||||
:param objs: type of objects to get
|
||||
:type objs: tuple[:class:`CapBaseObject`]
|
||||
:param split_path: path to discover
|
||||
:type split_path: :class:`list`
|
||||
:rtype: iter[:class:`BaseCapObject`]
|
||||
"""
|
||||
if Account in objs:
|
||||
self._restrict_level(split_path)
|
||||
|
||||
return self.iter_accounts()
|
||||
|
||||
def iter_accounts(self):
|
||||
"""
|
||||
Iter accounts.
|
||||
|
||||
:rtype: iter[:class:`Account`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_account(self, _id):
|
||||
def get_account(self, id):
|
||||
"""
|
||||
Get an account from its ID.
|
||||
|
||||
:param id: ID of the account
|
||||
:type id: :class:`str`
|
||||
:rtype: :class:`Account`
|
||||
:raises: :class:`AccountNotFound`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def iter_history(self, account):
|
||||
"""
|
||||
Iter history of transactions on a specific account.
|
||||
|
||||
@param account [Account]
|
||||
@return [iter(Transaction)]
|
||||
:param account: account to get history
|
||||
:type account: :class:`Account`
|
||||
:rtype: iter[:class:`Transaction`]
|
||||
:raises: :class:`AccountNotFound`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -120,8 +166,10 @@ class ICapBank(ICapCollection):
|
|||
"""
|
||||
Iter coming transactions on a specific account.
|
||||
|
||||
@param account [Account]
|
||||
@return [iter(Transaction)]
|
||||
:param account: account to get coming transactions
|
||||
:type account: :class:`Account`
|
||||
:rtype: iter[:class:`Transaction`]
|
||||
:raises: :class:`AccountNotFound`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -129,8 +177,10 @@ class ICapBank(ICapCollection):
|
|||
"""
|
||||
Iter recipients availables for a transfer from a specific account.
|
||||
|
||||
@param account [Account] account which initiate the transfer
|
||||
@return [iter(Recipient)]
|
||||
:param account: account which initiate the transfer
|
||||
:type account: :class:`Account`
|
||||
:rtype: iter[:class:`Recipient`]
|
||||
:raises: :class:`AccountNotFound`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -138,10 +188,15 @@ class ICapBank(ICapCollection):
|
|||
"""
|
||||
Make a transfer from an account to a recipient.
|
||||
|
||||
@param account [Account] account to take money
|
||||
@param recipient [Recipient] account to send money
|
||||
@param amount [float] amount
|
||||
@param reason [str] reason of transfer
|
||||
@return [Transfer] a Transfer object
|
||||
:param account: account to take money
|
||||
:type account: :class:`Account`
|
||||
:param recipient: account to send money
|
||||
:type recipient: :class:`Recipient`
|
||||
:param amount: amount
|
||||
:type amount: :class:`float`
|
||||
:param reason: reason of transfer
|
||||
:type reason: :class:`unicode`
|
||||
:rtype: :class:`Transfer`
|
||||
:raises: :class:`AccountNotFound`, :class:`TransferError`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
|
|
@ -18,14 +18,28 @@
|
|||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from weboob.tools.misc import iter_fields
|
||||
import datetime
|
||||
from dateutil.parser import parse as parse_dt
|
||||
from copy import deepcopy
|
||||
|
||||
from weboob.tools.misc import to_unicode
|
||||
from weboob.tools.ordereddict import OrderedDict
|
||||
|
||||
|
||||
__all__ = ['FieldNotFound', 'IBaseCap', 'NotAvailable', 'NotLoaded',
|
||||
'CapBaseObject']
|
||||
__all__ = ['FieldNotFound', 'NotAvailable', 'NotLoaded', 'IBaseCap',
|
||||
'Field', 'IntField', 'FloatField', 'StringField', 'BytesField',
|
||||
'DateField', 'DeltaField', 'CapBaseObject']
|
||||
|
||||
|
||||
class FieldNotFound(Exception):
|
||||
"""
|
||||
A field isn't found.
|
||||
|
||||
:param obj: object
|
||||
:type obj: :class:`CapBaseObject`
|
||||
:param field: field not found
|
||||
:type field: :class:`Field`
|
||||
"""
|
||||
def __init__(self, obj, field):
|
||||
Exception.__init__(self,
|
||||
u'Field "%s" not found for object %s' % (field, obj))
|
||||
|
|
@ -43,6 +57,9 @@ class NotAvailableMeta(type):
|
|||
|
||||
|
||||
class NotAvailable(object):
|
||||
"""
|
||||
Constant to use on non available fields.
|
||||
"""
|
||||
__metaclass__ = NotAvailableMeta
|
||||
|
||||
|
||||
|
|
@ -58,41 +75,186 @@ class NotLoadedMeta(type):
|
|||
|
||||
|
||||
class NotLoaded(object):
|
||||
"""
|
||||
Constant to use on not loaded fields.
|
||||
|
||||
When you use :func:`weboob.tools.backend.BaseBackend.fillobj` on a object based on :class:`CapBaseObject`,
|
||||
it will request all fields with this value.
|
||||
"""
|
||||
__metaclass__ = NotLoadedMeta
|
||||
|
||||
|
||||
class IBaseCap(object):
|
||||
pass
|
||||
"""
|
||||
This is the base class for all capabilities.
|
||||
|
||||
A capability may define abstract methods (which raise :class:`NotImplementedError`)
|
||||
with an explicit docstring to tell backends how to implement them.
|
||||
|
||||
Also, it may define some *objects*, using :class:`CapBaseObject`.
|
||||
"""
|
||||
|
||||
|
||||
class Field(object):
|
||||
"""
|
||||
Field of a :class:`CapBaseObject` class.
|
||||
|
||||
:param doc: docstring of the field
|
||||
:type doc: :class:`str`
|
||||
:param args: list of types accepted
|
||||
:param default: default value of this field. If not specified, :class:`NotLoaded` is used.
|
||||
"""
|
||||
_creation_counter = 0
|
||||
|
||||
def __init__(self, doc, *args, **kwargs):
|
||||
self.types = ()
|
||||
self.value = kwargs.get('default', NotLoaded)
|
||||
self.doc = doc
|
||||
|
||||
for arg in args:
|
||||
if isinstance(arg, type):
|
||||
self.types += (arg,)
|
||||
else:
|
||||
raise TypeError('Arguments must be types')
|
||||
|
||||
self._creation_counter = Field._creation_counter
|
||||
Field._creation_counter += 1
|
||||
|
||||
def convert(self, value):
|
||||
"""
|
||||
Convert value to the wanted one.
|
||||
"""
|
||||
return value
|
||||
|
||||
class IntField(Field):
|
||||
"""
|
||||
A field which accepts only :class:`int` and :class:`long` types.
|
||||
"""
|
||||
def __init__(self, doc, **kwargs):
|
||||
Field.__init__(self, doc, int, long, **kwargs)
|
||||
|
||||
def convert(self, value):
|
||||
return int(value)
|
||||
|
||||
class FloatField(Field):
|
||||
"""
|
||||
A field which accepts only :class:`float` type.
|
||||
"""
|
||||
def __init__(self, doc, **kwargs):
|
||||
Field.__init__(self, doc, float, **kwargs)
|
||||
|
||||
def convert(self, value):
|
||||
return float(value)
|
||||
|
||||
class StringField(Field):
|
||||
"""
|
||||
A field which accepts only :class:`unicode` strings.
|
||||
"""
|
||||
def __init__(self, doc, **kwargs):
|
||||
Field.__init__(self, doc, unicode, **kwargs)
|
||||
|
||||
def convert(self, value):
|
||||
return to_unicode(value)
|
||||
|
||||
class BytesField(Field):
|
||||
"""
|
||||
A field which accepts only :class:`str` strings.
|
||||
"""
|
||||
def __init__(self, doc, **kwargs):
|
||||
Field.__init__(self, doc, str, **kwargs)
|
||||
|
||||
def convert(self, value):
|
||||
if isinstance(value, unicode):
|
||||
value = value.encode('utf-8')
|
||||
return str(value)
|
||||
|
||||
class DateField(Field):
|
||||
"""
|
||||
A field which accepts only :class:`datetime.date` and :class:`datetime.datetime` types.
|
||||
"""
|
||||
def __init__(self, doc, **kwargs):
|
||||
Field.__init__(self, doc, datetime.date, datetime.datetime, **kwargs)
|
||||
|
||||
def convert(self, value):
|
||||
if isinstance(value, basestring):
|
||||
return parse_dt(value)
|
||||
return value
|
||||
|
||||
class TimeField(Field):
|
||||
"""
|
||||
A field which accepts only :class:`datetime.time` and :class:`datetime.time` types.
|
||||
"""
|
||||
def __init__(self, doc, **kwargs):
|
||||
Field.__init__(self, doc, datetime.time, datetime.datetime, **kwargs)
|
||||
|
||||
class DeltaField(Field):
|
||||
"""
|
||||
A field which accepts only :class:`datetime.timedelta` type.
|
||||
"""
|
||||
def __init__(self, doc, **kwargs):
|
||||
Field.__init__(self, doc, datetime.timedelta, **kwargs)
|
||||
|
||||
class _CapBaseObjectMeta(type):
|
||||
def __new__(cls, name, bases, attrs):
|
||||
fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)]
|
||||
fields.sort(key=lambda x: x[1]._creation_counter)
|
||||
|
||||
new_class = super(_CapBaseObjectMeta, cls).__new__(cls, name, bases, attrs)
|
||||
if new_class._fields is None:
|
||||
new_class._fields = OrderedDict()
|
||||
else:
|
||||
new_class._fields = deepcopy(new_class._fields)
|
||||
new_class._fields.update(fields)
|
||||
|
||||
assert new_class.__doc__ is not None
|
||||
if new_class.__doc__ is None:
|
||||
new_class.__doc__ = ''
|
||||
for name, field in fields:
|
||||
doc = '(%s) %s' % (', '.join([':class:`%s`' % v.__name__ for v in field.types]), field.doc)
|
||||
if field.value is not NotLoaded:
|
||||
doc += ' (default: %s)' % field.value
|
||||
new_class.__doc__ += '\n:var %s: %s' % (name, doc)
|
||||
return new_class
|
||||
|
||||
class CapBaseObject(object):
|
||||
FIELDS = None
|
||||
_attribs = None
|
||||
"""
|
||||
This is the base class for a capability object.
|
||||
|
||||
A capability interface may specify to return several kind of objects, to formalise
|
||||
retrieved information from websites.
|
||||
|
||||
As python is a flexible language where variables are not typed, we use a system to
|
||||
force backends to set wanted values on all fields. To do that, we use the :class:`Field`
|
||||
class and all derived ones.
|
||||
|
||||
For example::
|
||||
|
||||
class Transfer(CapBaseObject):
|
||||
" Transfer from an account to a recipient. "
|
||||
|
||||
amount = FloatField('Amount to transfer')
|
||||
date = Field('Date of transfer', basestring, date, datetime)
|
||||
origin = Field('Origin of transfer', int, long, basestring)
|
||||
recipient = Field('Recipient', int, long, basestring)
|
||||
|
||||
The docstring is mandatory.
|
||||
"""
|
||||
|
||||
__metaclass__ = _CapBaseObjectMeta
|
||||
_fields = None
|
||||
|
||||
def __init__(self, id, backend=None):
|
||||
self.id = id
|
||||
self.id = to_unicode(id)
|
||||
self.backend = backend
|
||||
self._fields = deepcopy(self._fields)
|
||||
|
||||
@property
|
||||
def fullid(self):
|
||||
"""
|
||||
Full ID of the object, in form '**ID@backend**'.
|
||||
"""
|
||||
return '%s@%s' % (self.id, self.backend)
|
||||
|
||||
def add_field(self, name, type, value=NotLoaded):
|
||||
"""
|
||||
Add a field in list, which needs to be of type @type.
|
||||
|
||||
@param name [str] name of field
|
||||
@param type [class] type accepted (can be a tuple of types)
|
||||
@param value [object] value set to attribute (default is NotLoaded)
|
||||
"""
|
||||
if not isinstance(self.FIELDS, list):
|
||||
self.FIELDS = []
|
||||
self.FIELDS.append(name)
|
||||
|
||||
if self._attribs is None:
|
||||
self._attribs = {}
|
||||
self._attribs[name] = self._AttribValue(type, value)
|
||||
|
||||
def __iscomplete__(self):
|
||||
"""
|
||||
Return True if the object is completed.
|
||||
|
|
@ -111,6 +273,9 @@ class CapBaseObject(object):
|
|||
def set_empty_fields(self, value, excepts=()):
|
||||
"""
|
||||
Set the same value on all empty fields.
|
||||
|
||||
:param value: value to set on all empty fields
|
||||
:param excepts: if specified, do not change fields listed
|
||||
"""
|
||||
for key, old_value in self.iter_fields():
|
||||
if old_value in (None, NotLoaded, NotAvailable) and \
|
||||
|
|
@ -119,22 +284,16 @@ class CapBaseObject(object):
|
|||
|
||||
def iter_fields(self):
|
||||
"""
|
||||
Iterate on the FIELDS keys and values.
|
||||
Iterate on the fields keys and values.
|
||||
|
||||
Can be overloaded to iterate on other things.
|
||||
|
||||
@return [iter(key,value)] iterator on key, value
|
||||
:rtype: iter[(key, value)]
|
||||
"""
|
||||
|
||||
if self.FIELDS is None:
|
||||
yield 'id', self.id
|
||||
for key, value in iter_fields(self):
|
||||
if key not in ('id', 'backend', 'FIELDS'):
|
||||
yield key, value
|
||||
else:
|
||||
yield 'id', self.id
|
||||
for attrstr in self.FIELDS:
|
||||
yield attrstr, getattr(self, attrstr)
|
||||
yield 'id', self.id
|
||||
for name, field in self._fields.iteritems():
|
||||
yield name, field.value
|
||||
|
||||
def __eq__(self, obj):
|
||||
if isinstance(obj, CapBaseObject):
|
||||
|
|
@ -142,29 +301,32 @@ class CapBaseObject(object):
|
|||
else:
|
||||
return False
|
||||
|
||||
class _AttribValue(object):
|
||||
def __init__(self, type, value):
|
||||
self.type = type
|
||||
self.value = value
|
||||
|
||||
def __getattr__(self, name):
|
||||
if self._attribs is not None and name in self._attribs:
|
||||
return self._attribs[name].value
|
||||
if self._fields is not None and name in self._fields:
|
||||
return self._fields[name].value
|
||||
else:
|
||||
raise AttributeError, "'%s' object has no attribute '%s'" % (
|
||||
self.__class__.__name__, name)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
try:
|
||||
attr = (self._attribs or {})[name]
|
||||
attr = (self._fields or {})[name]
|
||||
except KeyError:
|
||||
object.__setattr__(self, name, value)
|
||||
else:
|
||||
if not isinstance(value, attr.type) and \
|
||||
try:
|
||||
# Try to convert value to the wanted one.
|
||||
value = attr.convert(value)
|
||||
except Exception:
|
||||
# error during conversion, it will probably not
|
||||
# match the wanted following types, so we'll
|
||||
# raise ValueError.
|
||||
pass
|
||||
if not isinstance(value, attr.types) and \
|
||||
value is not NotLoaded and \
|
||||
value is not NotAvailable and \
|
||||
value is not None:
|
||||
raise ValueError(
|
||||
'Value for "%s" needs to be of type %r, not %r' % (
|
||||
name, attr.type, type(value)))
|
||||
name, attr.types, type(value)))
|
||||
attr.value = value
|
||||
|
|
|
|||
|
|
@ -17,74 +17,133 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from datetime import datetime, date
|
||||
from .base import CapBaseObject
|
||||
from .base import CapBaseObject, StringField, DateField, FloatField
|
||||
from .collection import ICapCollection
|
||||
|
||||
|
||||
__all__ = ['Subscription', 'SubscriptionNotFound', 'ICapBill', 'Detail']
|
||||
__all__ = ['SubscriptionNotFound', 'BillNotFound', 'Detail', 'Bill', 'Subscription', 'ICapBill']
|
||||
|
||||
|
||||
class SubscriptionNotFound(Exception):
|
||||
def __init__(self, msg=None):
|
||||
if msg is None:
|
||||
msg = 'Subscription not found'
|
||||
"""
|
||||
Raised when a subscription is not found.
|
||||
"""
|
||||
def __init__(self, msg='Subscription not found'):
|
||||
Exception.__init__(self, msg)
|
||||
|
||||
|
||||
class BillNotFound(Exception):
|
||||
def __init__(self, msg=None):
|
||||
if msg is None:
|
||||
msg = 'Bill not found'
|
||||
"""
|
||||
Raised when a bill is not found.
|
||||
"""
|
||||
def __init__(self, msg='Bill not found'):
|
||||
Exception.__init__(self, msg)
|
||||
|
||||
|
||||
class Detail(CapBaseObject):
|
||||
"""
|
||||
Detail of a subscription
|
||||
"""
|
||||
label = StringField('label of the detail line')
|
||||
infos = StringField('information')
|
||||
datetime = DateField('date information')
|
||||
price = FloatField('price')
|
||||
|
||||
def __init__(self):
|
||||
CapBaseObject.__init__(self, 0)
|
||||
self.add_field('label', basestring)
|
||||
self.add_field('infos', basestring)
|
||||
self.add_field('datetime', datetime)
|
||||
self.add_field('price', float)
|
||||
|
||||
class Bill(CapBaseObject):
|
||||
"""
|
||||
Bill.
|
||||
"""
|
||||
date = DateField('date of the bill')
|
||||
format = StringField('format of the bill')
|
||||
label = StringField('label of bill')
|
||||
idparent = StringField('id of the parent subscription')
|
||||
|
||||
def __init__(self):
|
||||
CapBaseObject.__init__(self, 0)
|
||||
self.add_field('date', date)
|
||||
self.add_field('format', basestring)
|
||||
self.add_field('label', basestring)
|
||||
self.add_field('idparent', basestring)
|
||||
|
||||
class Subscription(CapBaseObject):
|
||||
def __init__(self, id):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('label', basestring)
|
||||
self.add_field('subscriber', basestring)
|
||||
"""
|
||||
Subscription to a service.
|
||||
"""
|
||||
label = StringField('label of subscription')
|
||||
subscriber = StringField('whe has subscribed')
|
||||
|
||||
class ICapBill(ICapCollection):
|
||||
def iter_resources(self, objs, split_path):
|
||||
"""
|
||||
Iter resources. Will return :func:`iter_subscriptions`.
|
||||
"""
|
||||
if Subscription in objs:
|
||||
self._restrict_level(split_path)
|
||||
|
||||
return self.iter_subscription()
|
||||
|
||||
def iter_subscription(self):
|
||||
"""
|
||||
Iter subscriptions.
|
||||
|
||||
:rtype: iter[:class:`Subscription`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_subscription(self, _id):
|
||||
"""
|
||||
Get a subscription.
|
||||
|
||||
:param _id: ID of subscription
|
||||
:rtype: :class:`Subscription`
|
||||
:raises: :class:`SubscriptionNotFound`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def iter_history(self, subscription):
|
||||
"""
|
||||
Iter history of a subscription.
|
||||
|
||||
:param subscription: subscription to get history
|
||||
:type subscription: :class:`Subscription`
|
||||
:rtype: iter[:class:`Detail`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_bill(self, id):
|
||||
"""
|
||||
Get a bill.
|
||||
|
||||
:param id: ID of bill
|
||||
:rtype: :class:`Bill`
|
||||
:raises: :class:`BillNotFound`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def download_bill(self, id):
|
||||
"""
|
||||
Download a bill.
|
||||
|
||||
:param id: ID of bill
|
||||
:rtype: str
|
||||
:raises: :class:`BillNotFound`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def iter_bills(self, subscription):
|
||||
"""
|
||||
Iter bills.
|
||||
|
||||
:param subscription: subscription to get bills
|
||||
:type subscription: :class:`Subscription`
|
||||
:rtype: iter[:class:`Bill`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_details(self, subscription):
|
||||
"""
|
||||
Get details of a subscription.
|
||||
|
||||
:param subscription: subscription to get bills
|
||||
:type subscription: :class:`Subscription`
|
||||
:rtype: iter[:class:`Detail`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
|
|
@ -17,30 +17,49 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from .base import IBaseCap, CapBaseObject
|
||||
from .base import IBaseCap, CapBaseObject, Field, StringField, DateField, \
|
||||
IntField, DeltaField
|
||||
|
||||
|
||||
__all__ = ['ICapBugTracker']
|
||||
__all__ = ['IssueError', 'Project', 'User', 'Version', 'Status', 'Attachment',
|
||||
'Change', 'Update', 'Issue', 'Query', 'ICapBugTracker']
|
||||
|
||||
|
||||
class IssueError(Exception):
|
||||
pass
|
||||
"""
|
||||
Raised when there is an error with an issue.
|
||||
"""
|
||||
|
||||
class Project(CapBaseObject):
|
||||
"""
|
||||
Represents a project.
|
||||
"""
|
||||
name = StringField('Name of the project')
|
||||
members = Field('Members of projects', list)
|
||||
versions = Field('List of versions available for this project', list)
|
||||
categories = Field('All categories', list)
|
||||
statuses = Field('Available statuses for issues', list)
|
||||
|
||||
def __init__(self, id, name):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('name', unicode, name)
|
||||
self.add_field('members', list)
|
||||
self.add_field('versions', list)
|
||||
self.add_field('categories', list)
|
||||
self.add_field('statuses', list)
|
||||
self.name = name
|
||||
|
||||
def __repr__(self):
|
||||
return '<Project %r>' % self.name
|
||||
|
||||
def find_user(self, id, name):
|
||||
"""
|
||||
Find a user from its ID.
|
||||
|
||||
If not found, create a :class:`User` with the specified name.
|
||||
|
||||
:param id: ID of user
|
||||
:type id: str
|
||||
:param name: Name of user
|
||||
:type name: str
|
||||
:rtype: :class:`User`
|
||||
"""
|
||||
for user in self.members:
|
||||
if user.id == id:
|
||||
return user
|
||||
|
|
@ -49,6 +68,17 @@ class Project(CapBaseObject):
|
|||
return User(id, name)
|
||||
|
||||
def find_version(self, id, name):
|
||||
"""
|
||||
Find a version from an ID.
|
||||
|
||||
If not found, create a :class:`Version` with the specified name.
|
||||
|
||||
:param id: ID of version
|
||||
:type id: str
|
||||
:param name: Name of version
|
||||
:type name: str
|
||||
:rtype: :class:`Version`
|
||||
"""
|
||||
for version in self.versions:
|
||||
if version.id == id:
|
||||
return version
|
||||
|
|
@ -57,6 +87,13 @@ class Project(CapBaseObject):
|
|||
return Version(id, name)
|
||||
|
||||
def find_status(self, name):
|
||||
"""
|
||||
Find a status from a name.
|
||||
|
||||
:param name: Name of status
|
||||
:type name: str
|
||||
:rtype: :class:`Status`
|
||||
"""
|
||||
for status in self.statuses:
|
||||
if status.name == name:
|
||||
return status
|
||||
|
|
@ -65,98 +102,129 @@ class Project(CapBaseObject):
|
|||
return None
|
||||
|
||||
class User(CapBaseObject):
|
||||
"""
|
||||
User.
|
||||
"""
|
||||
name = StringField('Name of user')
|
||||
|
||||
def __init__(self, id, name):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('name', unicode, name)
|
||||
self.name = name
|
||||
|
||||
def __repr__(self):
|
||||
return '<User %r>' % self.name
|
||||
|
||||
class Version(CapBaseObject):
|
||||
"""
|
||||
Version of a project.
|
||||
"""
|
||||
name = StringField('Name of version')
|
||||
|
||||
def __init__(self, id, name):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('name', unicode, name)
|
||||
self.name = name
|
||||
|
||||
def __repr__(self):
|
||||
return '<Version %r>' % self.name
|
||||
|
||||
class Status(CapBaseObject):
|
||||
"""
|
||||
Status of an issue.
|
||||
|
||||
**VALUE_** constants are the primary status
|
||||
types.
|
||||
"""
|
||||
(VALUE_NEW,
|
||||
VALUE_PROGRESS,
|
||||
VALUE_RESOLVED,
|
||||
VALUE_REJECTED) = range(4)
|
||||
|
||||
name = StringField('Name of status')
|
||||
value = IntField('Value of status (constants VALUE_*)')
|
||||
|
||||
def __init__(self, id, name, value):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('name', unicode, name)
|
||||
self.add_field('value', int, value)
|
||||
self.name = name
|
||||
self.value = value
|
||||
|
||||
def __repr__(self):
|
||||
return '<Status %r>' % self.name
|
||||
|
||||
class Attachment(CapBaseObject):
|
||||
def __init__(self, id):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('filename', basestring)
|
||||
self.add_field('url', basestring)
|
||||
"""
|
||||
Attachment of an issue.
|
||||
"""
|
||||
filename = StringField('Filename')
|
||||
url = StringField('Direct URL to attachment')
|
||||
|
||||
def __repr__(self):
|
||||
return '<Attachment %r>' % self.filename
|
||||
|
||||
class Change(CapBaseObject):
|
||||
def __init__(self, id):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('field', unicode)
|
||||
self.add_field('last', unicode)
|
||||
self.add_field('new', unicode)
|
||||
"""
|
||||
A change of an update.
|
||||
"""
|
||||
field = StringField('What field has been changed')
|
||||
last = StringField('Last value of field')
|
||||
new = StringField('New value of field')
|
||||
|
||||
class Update(CapBaseObject):
|
||||
def __init__(self, id):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('author', User)
|
||||
self.add_field('date', datetime)
|
||||
self.add_field('hours', timedelta)
|
||||
self.add_field('message', unicode)
|
||||
self.add_field('attachments', (list,tuple)) # Attachment
|
||||
self.add_field('changes', (list,tuple)) # Change
|
||||
"""
|
||||
Represents an update of an issue.
|
||||
"""
|
||||
author = Field('Author of update', User)
|
||||
date = DateField('Date of update')
|
||||
hours = DeltaField('Time activity')
|
||||
message = StringField('Log message')
|
||||
attachments = Field('Files attached to update', list, tuple)
|
||||
changes = Field('List of changes', list, tuple)
|
||||
|
||||
def __repr__(self):
|
||||
return '<Update %r>' % self.id
|
||||
|
||||
class Issue(CapBaseObject):
|
||||
def __init__(self, id):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('project', Project)
|
||||
self.add_field('title', unicode)
|
||||
self.add_field('body', unicode)
|
||||
self.add_field('creation', datetime)
|
||||
self.add_field('updated', datetime)
|
||||
self.add_field('attachments', (list,tuple))
|
||||
self.add_field('history', (list,tuple))
|
||||
self.add_field('author', User)
|
||||
self.add_field('assignee', User)
|
||||
self.add_field('category', unicode)
|
||||
self.add_field('version', Version)
|
||||
self.add_field('status', Status)
|
||||
"""
|
||||
Represents an issue.
|
||||
"""
|
||||
project = Field('Project of this issue', Project)
|
||||
title = StringField('Title of issue')
|
||||
body = StringField('Text of issue')
|
||||
creation = DateField('Date when this issue has been created')
|
||||
updated = DateField('Date when this issue has been updated for the last time')
|
||||
attachments = Field('List of attached files', list, tuple)
|
||||
history = Field('History of updates', list, tuple)
|
||||
author = Field('Author of this issue', User)
|
||||
assignee = Field('User assigned to this issue', User)
|
||||
category = StringField('Name of the category')
|
||||
version = Field('Target version of this issue', Version)
|
||||
status = Field('Status of this issue', Status)
|
||||
|
||||
class Query(CapBaseObject):
|
||||
"""
|
||||
Query to find an issue.
|
||||
"""
|
||||
project = StringField('Filter on projects')
|
||||
title = StringField('Filter on titles')
|
||||
author = StringField('Filter on authors')
|
||||
assignee = StringField('Filter on assignees')
|
||||
version = StringField('Filter on versions')
|
||||
category = StringField('Filter on categories')
|
||||
status = StringField('Filter on statuses')
|
||||
|
||||
def __init__(self):
|
||||
CapBaseObject.__init__(self, '')
|
||||
self.add_field('project', unicode)
|
||||
self.add_field('title', unicode)
|
||||
self.add_field('author', unicode)
|
||||
self.add_field('assignee', unicode)
|
||||
self.add_field('version', unicode)
|
||||
self.add_field('category', unicode)
|
||||
self.add_field('status', unicode)
|
||||
|
||||
class ICapBugTracker(IBaseCap):
|
||||
"""
|
||||
Bug trackers websites.
|
||||
"""
|
||||
def iter_issues(self, query):
|
||||
"""
|
||||
Iter issues with optionnal patterns.
|
||||
|
||||
@param query [Query]
|
||||
@return [iter(Issue)] issues
|
||||
:param query: query
|
||||
:type query: :class:`Query`
|
||||
:rtype: iter[:class:`Issue`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -164,7 +232,8 @@ class ICapBugTracker(IBaseCap):
|
|||
"""
|
||||
Get an issue from its ID.
|
||||
|
||||
@return Issue
|
||||
:param id: ID of issue
|
||||
:rtype: :class:`Issue`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -172,13 +241,19 @@ class ICapBugTracker(IBaseCap):
|
|||
"""
|
||||
Create an empty issue on the given project.
|
||||
|
||||
@return [Issue] the created issue.
|
||||
:param project: project
|
||||
:type project: :class:`Project`
|
||||
:returns: the created issue
|
||||
:rtype: :class:`Issue`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def post_issue(self, issue):
|
||||
"""
|
||||
Post an issue to create or update it.
|
||||
|
||||
:param issue: issue to create or update
|
||||
:type issue: :class:`Issue`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -186,14 +261,19 @@ class ICapBugTracker(IBaseCap):
|
|||
"""
|
||||
Add an update to an issue.
|
||||
|
||||
@param issue [id,Issue] issue or id of issue
|
||||
@param update [Update] an Update object
|
||||
:param issue: issue or id of issue
|
||||
:type issue: :class:`Issue`
|
||||
:param update: an Update object
|
||||
:type update: :class:`Update`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def remove_issue(self, issue):
|
||||
"""
|
||||
Remove an issue.
|
||||
|
||||
:param issue: issue
|
||||
:type issue: :class:`Issue`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -201,7 +281,7 @@ class ICapBugTracker(IBaseCap):
|
|||
"""
|
||||
Iter projects.
|
||||
|
||||
@return [iter(Project)] projects
|
||||
:rtype: iter[:class:`Project`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -209,6 +289,6 @@ class ICapBugTracker(IBaseCap):
|
|||
"""
|
||||
Get a project from its ID.
|
||||
|
||||
@return [Project]
|
||||
:rtype: :class:`Project`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
|
|
@ -20,31 +20,60 @@
|
|||
|
||||
import datetime
|
||||
|
||||
from .base import IBaseCap, CapBaseObject
|
||||
from .base import IBaseCap, CapBaseObject, StringField, DateField
|
||||
|
||||
|
||||
__all__ = ['ChatException', 'ICapChat']
|
||||
__all__ = ['ChatException', 'ChatMessage', 'ICapChat']
|
||||
|
||||
|
||||
class ChatException(Exception):
|
||||
pass
|
||||
"""
|
||||
Exception raised when there is a problem with the chat.
|
||||
"""
|
||||
|
||||
|
||||
class ChatMessage(CapBaseObject):
|
||||
"""
|
||||
Message on the chat.
|
||||
"""
|
||||
id_from = StringField('ID of sender')
|
||||
id_to = StringField('ID of recipient')
|
||||
message = StringField('Content of message')
|
||||
date = DateField('Date when the message has been sent')
|
||||
|
||||
def __init__(self, id_from, id_to, message, date=None):
|
||||
CapBaseObject.__init__(self, '%s.%s' % (id_from, id_to))
|
||||
self.add_field('id_from', basestring, id_from)
|
||||
self.add_field('id_to', basestring, id_to)
|
||||
self.add_field('message', basestring, message)
|
||||
self.add_field('date', datetime.datetime, date)
|
||||
self.id_from = id_from
|
||||
self.id_to = id_to
|
||||
self.message = message
|
||||
self.date = date
|
||||
|
||||
if self.date is None:
|
||||
self.date = datetime.datetime.utcnow()
|
||||
|
||||
|
||||
class ICapChat(IBaseCap):
|
||||
"""
|
||||
Websites with a chat system.
|
||||
"""
|
||||
def iter_chat_messages(self, _id=None):
|
||||
"""
|
||||
Iter messages.
|
||||
|
||||
:param _id: optional parameter to only get messages
|
||||
from a given contact.
|
||||
:type _id: str
|
||||
:rtype: iter[:class:`ChatMessage`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def send_chat_message(self, _id, message):
|
||||
"""
|
||||
Send a message to a contact.
|
||||
|
||||
:param _id: ID of recipient
|
||||
:type _id: str
|
||||
:param message: message to send
|
||||
:type message: str
|
||||
:raises: :class:`ChatException`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
|
|
@ -18,14 +18,17 @@
|
|||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from .base import IBaseCap, CapBaseObject
|
||||
from .base import IBaseCap, CapBaseObject, Field, StringField, BytesField, IntField
|
||||
from weboob.tools.ordereddict import OrderedDict
|
||||
|
||||
|
||||
__all__ = ['ICapContact', 'Contact']
|
||||
__all__ = ['ProfileNode', 'ContactPhoto', 'Contact', 'QueryError', 'Query', 'ICapContact']
|
||||
|
||||
|
||||
class ProfileNode(object):
|
||||
"""
|
||||
Node of a :class:`Contact` profile.
|
||||
"""
|
||||
HEAD = 0x01
|
||||
SECTION = 0x02
|
||||
|
||||
|
|
@ -41,14 +44,19 @@ class ProfileNode(object):
|
|||
|
||||
|
||||
class ContactPhoto(CapBaseObject):
|
||||
"""
|
||||
Photo of a contact.
|
||||
"""
|
||||
name = StringField('Name of the photo')
|
||||
url = StringField('Direct URL to photo')
|
||||
data = BytesField('Data of photo')
|
||||
thumbnail_url = StringField('Direct URL to thumbnail')
|
||||
thumbnail_data = BytesField('Data of thumbnail')
|
||||
hidden = Field('True if the photo is hidden on website', bool)
|
||||
|
||||
def __init__(self, name):
|
||||
CapBaseObject.__init__(self, name)
|
||||
self.add_field('name', basestring, name)
|
||||
self.add_field('url', basestring)
|
||||
self.add_field('data', str)
|
||||
self.add_field('thumbnail_url', basestring)
|
||||
self.add_field('thumbnail_data', basestring)
|
||||
self.add_field('hidden', bool, False)
|
||||
self.name = name
|
||||
|
||||
def __iscomplete__(self):
|
||||
return (self.data and (not self.thumbnail_url or self.thumbnail_data))
|
||||
|
|
@ -63,22 +71,35 @@ class ContactPhoto(CapBaseObject):
|
|||
|
||||
|
||||
class Contact(CapBaseObject):
|
||||
"""
|
||||
A contact.
|
||||
"""
|
||||
STATUS_ONLINE = 0x001
|
||||
STATUS_AWAY = 0x002
|
||||
STATUS_OFFLINE = 0x004
|
||||
STATUS_ALL = 0xfff
|
||||
|
||||
name = StringField('Name of contact')
|
||||
status = IntField('Status of contact (STATUS_* constants)')
|
||||
url = StringField('URL to the profile of contact')
|
||||
status_msg = StringField('Message of status')
|
||||
summary = StringField('Description of contact')
|
||||
photos = Field('List of photos', dict, default=OrderedDict())
|
||||
profile = Field('Contact profile', dict)
|
||||
|
||||
def __init__(self, id, name, status):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('name', basestring, name)
|
||||
self.add_field('status', int, status)
|
||||
self.add_field('url', basestring)
|
||||
self.add_field('status_msg', basestring)
|
||||
self.add_field('summary', basestring)
|
||||
self.add_field('photos', dict, OrderedDict())
|
||||
self.add_field('profile', dict)
|
||||
self.name = name
|
||||
self.status = status
|
||||
|
||||
def set_photo(self, name, **kwargs):
|
||||
"""
|
||||
Set photo of contact.
|
||||
|
||||
:param name: name of photo
|
||||
:type name: str
|
||||
:param kwargs: See :class:`ContactPhoto` to know what other parameters you can use
|
||||
"""
|
||||
if not name in self.photos:
|
||||
self.photos[name] = ContactPhoto(name)
|
||||
|
||||
|
|
@ -88,13 +109,20 @@ class Contact(CapBaseObject):
|
|||
|
||||
|
||||
class QueryError(Exception):
|
||||
pass
|
||||
"""
|
||||
Raised when unable to send a query to a contact.
|
||||
"""
|
||||
|
||||
|
||||
class Query(CapBaseObject):
|
||||
"""
|
||||
Query to send to a contact.
|
||||
"""
|
||||
message = StringField('Message received')
|
||||
|
||||
def __init__(self, id, message):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('message', basestring, message)
|
||||
self.message = message
|
||||
|
||||
|
||||
class ICapContact(IBaseCap):
|
||||
|
|
@ -102,9 +130,11 @@ class ICapContact(IBaseCap):
|
|||
"""
|
||||
Iter contacts
|
||||
|
||||
@param status get only contacts with the specified status
|
||||
@param ids if set, get the specified contacts
|
||||
@return iterator over the contacts found
|
||||
:param status: get only contacts with the specified status
|
||||
:type status: Contact.STATUS_*
|
||||
:param ids: if set, get the specified contacts
|
||||
:type ids: list[str]
|
||||
:rtype: iter[:class:`Contact`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -116,8 +146,9 @@ class ICapContact(IBaseCap):
|
|||
with the proper values, but it might be overloaded
|
||||
by backends.
|
||||
|
||||
@param id the ID requested
|
||||
@return the Contact object, or None if not found
|
||||
:param id: the ID requested
|
||||
:type id: str
|
||||
:rtype: :class:`Contact` or None if not found
|
||||
"""
|
||||
|
||||
l = self.iter_contacts(ids=[id])
|
||||
|
|
@ -130,9 +161,10 @@ class ICapContact(IBaseCap):
|
|||
"""
|
||||
Send a query to a contact
|
||||
|
||||
@param id the ID of contact
|
||||
@return a Query object
|
||||
@except QueryError
|
||||
:param id: the ID of contact
|
||||
:type id: str
|
||||
:rtype: :class:`Query`
|
||||
:raises: :class:`QueryError`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -140,8 +172,9 @@ class ICapContact(IBaseCap):
|
|||
"""
|
||||
Get personal notes about a contact
|
||||
|
||||
@param id the ID of the contact
|
||||
@return a unicode object
|
||||
:param id: the ID of the contact
|
||||
:type id: str
|
||||
:rtype: unicode
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
|
@ -149,7 +182,8 @@ class ICapContact(IBaseCap):
|
|||
"""
|
||||
Set personal notes about a contact
|
||||
|
||||
@param id the ID of the contact
|
||||
@param notes the unicode object to save as notes
|
||||
:param id: the ID of the contact
|
||||
:type id: str
|
||||
:returns: the unicode object to save as notes
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
|
|
|||
|
|
@ -18,37 +18,74 @@
|
|||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from .base import IBaseCap, CapBaseObject
|
||||
from datetime import datetime
|
||||
from .base import IBaseCap, CapBaseObject, StringField, DateField, Field
|
||||
|
||||
|
||||
__all__ = ['Content', 'Revision', 'ICapContent']
|
||||
|
||||
|
||||
class Content(CapBaseObject):
|
||||
def __init__(self, id):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('title', basestring)
|
||||
self.add_field('author', basestring)
|
||||
self.add_field('content', basestring)
|
||||
self.add_field('revision', basestring)
|
||||
"""
|
||||
Content object.
|
||||
"""
|
||||
title = StringField('Title of content')
|
||||
author = StringField('Original author of content')
|
||||
content = StringField('Body')
|
||||
revision = StringField('ID of revision')
|
||||
|
||||
class Revision(CapBaseObject):
|
||||
def __init__(self, _id):
|
||||
CapBaseObject.__init__(self, _id)
|
||||
self.add_field('author', basestring)
|
||||
self.add_field('comment', basestring)
|
||||
self.add_field('revision', basestring)
|
||||
self.add_field('timestamp', datetime)
|
||||
self.add_field('minor', bool)
|
||||
|
||||
|
||||
"""
|
||||
Revision of a change on a content.
|
||||
"""
|
||||
author = StringField('Author of revision')
|
||||
comment = StringField('Comment log about revision')
|
||||
timestamp = DateField('Date of revision')
|
||||
minor = Field('Is this change minor?', bool)
|
||||
|
||||
class ICapContent(IBaseCap):
|
||||
def get_content(self, id, revision=None):
|
||||
"""
|
||||
Get a content from an ID.
|
||||
|
||||
:param id: ID of content
|
||||
:type id: str
|
||||
:param revision: if given, get the content at this revision
|
||||
:type revision: :class:`Revision`
|
||||
:rtype: :class:`Content`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def iter_revisions(self, id, max_results=10):
|
||||
"""
|
||||
Iter revisions of a content.
|
||||
|
||||
:param id: id of content
|
||||
:type id: str
|
||||
:param max_results: maximum results
|
||||
:type max_results: int
|
||||
:rtype: iter[:class:`Revision`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def push_content(self, content, message=None, minor=False):
|
||||
"""
|
||||
Push a new revision of a content.
|
||||
|
||||
:param content: object to push
|
||||
:type content: :class:`Content`
|
||||
:param message: log message to associate to new revision
|
||||
:type message: str
|
||||
:param minor: this is a minor revision
|
||||
:type minor: bool
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_content_preview(self, content):
|
||||
"""
|
||||
Get a HTML preview of a content.
|
||||
|
||||
:param content: content object
|
||||
:type content: :class:`Content`
|
||||
:rtype: str
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
|
|
@ -18,55 +18,103 @@
|
|||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import datetime
|
||||
|
||||
from .base import IBaseCap, CapBaseObject
|
||||
from .base import IBaseCap, CapBaseObject, Field, StringField, DateField
|
||||
from .contact import Contact
|
||||
|
||||
|
||||
__all__ = ['ICapDating']
|
||||
__all__ = ['OptimizationNotFound', 'Optimization', 'Event', 'ICapDating']
|
||||
|
||||
|
||||
class OptimizationNotFound(Exception):
|
||||
pass
|
||||
"""
|
||||
Raised when an optimization is not found.
|
||||
"""
|
||||
|
||||
|
||||
class Optimization(object):
|
||||
# Configuration of optim can be made by Value*s in this dict.
|
||||
"""
|
||||
Optimization.
|
||||
|
||||
:var CONFIG: Configuration of optim can be made by
|
||||
:class:`weboob.tools.value.Value` objects
|
||||
in this dict.
|
||||
"""
|
||||
CONFIG = {}
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
Start optimization.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
Stop optimization.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def is_running(self):
|
||||
"""
|
||||
Know if the optimization is currently running.
|
||||
|
||||
:rtype: bool
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_config(self):
|
||||
"""
|
||||
Get config of this optimization.
|
||||
|
||||
:rtype: dict
|
||||
"""
|
||||
return None
|
||||
|
||||
def set_config(self, params):
|
||||
"""
|
||||
Set config of this optimization.
|
||||
|
||||
:param params: parameters
|
||||
:type params: dict
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class Event(CapBaseObject):
|
||||
def __init__(self, id):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('date', (datetime.datetime))
|
||||
self.add_field('contact', Contact)
|
||||
self.add_field('type', basestring)
|
||||
self.add_field('message', basestring)
|
||||
"""
|
||||
A dating event (for example a visite, a query received, etc.)
|
||||
"""
|
||||
date = DateField('Date of event')
|
||||
contact = Field('Contact related to this event', Contact)
|
||||
type = StringField('Type of event')
|
||||
message = StringField('Message of the event')
|
||||
|
||||
class ICapDating(IBaseCap):
|
||||
"""
|
||||
Capability for dating websites.
|
||||
"""
|
||||
def init_optimizations(self):
|
||||
"""
|
||||
Initialization of optimizations.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def add_optimization(self, name, optim):
|
||||
"""
|
||||
Add an optimization.
|
||||
|
||||
:param name: name of optimization
|
||||
:type name: str
|
||||
:param optim: optimization
|
||||
:type optim: :class:`Optimization`
|
||||
"""
|
||||
setattr(self, 'OPTIM_%s' % name, optim)
|
||||
|
||||
def iter_optimizations(self, *optims):
|
||||
def iter_optimizations(self):
|
||||
"""
|
||||
Iter optimizations.
|
||||
|
||||
:rtype: iter[:class:`Optimization`]
|
||||
"""
|
||||
for attr_name in dir(self):
|
||||
if not attr_name.startswith('OPTIM_'):
|
||||
continue
|
||||
|
|
@ -77,6 +125,13 @@ class ICapDating(IBaseCap):
|
|||
yield attr_name[6:], attr
|
||||
|
||||
def get_optimization(self, optim):
|
||||
"""
|
||||
Get an optimization from a name.
|
||||
|
||||
:param optim: name of optimization
|
||||
:type optim: str
|
||||
:rtype: :class:`Optimization`
|
||||
"""
|
||||
optim = optim.upper()
|
||||
if not hasattr(self, 'OPTIM_%s' % optim):
|
||||
raise OptimizationNotFound()
|
||||
|
|
@ -84,4 +139,9 @@ class ICapDating(IBaseCap):
|
|||
return getattr(self, 'OPTIM_%s' % optim)
|
||||
|
||||
def iter_events(self):
|
||||
"""
|
||||
Iter events.
|
||||
|
||||
:rtype: iter[:class:`Event`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
|
|
@ -17,31 +17,39 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from weboob.tools.capabilities.thumbnail import Thumbnail
|
||||
from .base import IBaseCap, CapBaseObject, NotLoaded
|
||||
from .base import IBaseCap, CapBaseObject, NotLoaded, Field, StringField, \
|
||||
BytesField, IntField, FloatField, DateField
|
||||
|
||||
__all__ = ['Thumbnail', 'ICapGallery', 'BaseGallery', 'BaseImage']
|
||||
__all__ = ['BaseGallery', 'BaseImage', 'ICapGallery']
|
||||
|
||||
|
||||
class BaseGallery(CapBaseObject):
|
||||
"""
|
||||
Represents a gallery.
|
||||
|
||||
This object has to be inherited to specify how to calculate the URL of the gallery from its ID.
|
||||
"""
|
||||
title = StringField('Title of gallery')
|
||||
url = StringField('Direct URL to gallery')
|
||||
description = StringField('Description of gallery')
|
||||
cardinality = IntField('Cardinality of gallery')
|
||||
date = DateField('Date of gallery')
|
||||
rating = FloatField('Rating of this gallery')
|
||||
rating_max = FloatField('Max rating available')
|
||||
thumbnail = Field('Thumbnail', Thumbnail)
|
||||
|
||||
def __init__(self, _id, title=NotLoaded, url=NotLoaded, cardinality=NotLoaded, date=NotLoaded,
|
||||
rating=NotLoaded, rating_max=NotLoaded, thumbnail=NotLoaded, thumbnail_url=None, nsfw=False):
|
||||
CapBaseObject.__init__(self, unicode(_id))
|
||||
|
||||
self.add_field('title', basestring, title)
|
||||
self.add_field('url', basestring, url)
|
||||
self.add_field('description', basestring)
|
||||
self.add_field('cardinality', int)
|
||||
self.add_field('date', datetime, date)
|
||||
self.add_field('rating', (int, long, float), rating)
|
||||
self.add_field('rating_max', (int, long, float), rating_max)
|
||||
self.add_field('thumbnail', Thumbnail, thumbnail)
|
||||
self.title = title
|
||||
self.url = url
|
||||
self.date = date
|
||||
self.rating = rating
|
||||
self.rating_max = rating_max
|
||||
self.thumbnail = thumbnail
|
||||
|
||||
@classmethod
|
||||
def id2url(cls, _id):
|
||||
|
|
@ -50,24 +58,39 @@ class BaseGallery(CapBaseObject):
|
|||
|
||||
@property
|
||||
def page_url(self):
|
||||
"""
|
||||
Get URL to page of this gallery.
|
||||
"""
|
||||
return self.id2url(self.id)
|
||||
|
||||
def iter_image(self):
|
||||
"""
|
||||
Iter images.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class BaseImage(CapBaseObject):
|
||||
"""
|
||||
Base class for images.
|
||||
"""
|
||||
index = IntField('Usually page number')
|
||||
thumbnail = Field('Thumbnail of the image', Thumbnail)
|
||||
url = StringField('Direct URL to image')
|
||||
ext = StringField('Extension of image')
|
||||
data = BytesField('Data of image')
|
||||
gallery = Field('Reference to the Gallery object', BaseGallery)
|
||||
|
||||
def __init__(self, _id, index=None, thumbnail=NotLoaded, url=NotLoaded,
|
||||
ext=NotLoaded, gallery=None):
|
||||
|
||||
CapBaseObject.__init__(self, unicode(_id))
|
||||
|
||||
self.add_field('index', int, index) # usually page number
|
||||
self.add_field('thumbnail', Thumbnail, thumbnail)
|
||||
self.add_field('url', basestring, url)
|
||||
self.add_field('ext', basestring, ext)
|
||||
self.add_field('data', str)
|
||||
self.add_field('gallery', BaseGallery, gallery)
|
||||
self.index = index
|
||||
self.thumbnail = thumbnail
|
||||
self.url = url
|
||||
self.ext = ext
|
||||
self.gallery = gallery
|
||||
|
||||
def __str__(self):
|
||||
return self.url
|
||||
|
|
@ -92,10 +115,15 @@ class ICapGallery(IBaseCap):
|
|||
"""
|
||||
Iter results of a search on a pattern.
|
||||
|
||||
@param pattern [str] pattern to search on
|
||||
@param sortby [enum] sort by...
|
||||
@param nsfw [bool] include non-suitable for work videos if True
|
||||
@param max_results [int] maximum number of results to return
|
||||
:param pattern: pattern to search on
|
||||
:type pattern: str
|
||||
:param sortby: sort by...
|
||||
:type sortby: SEARCH_*
|
||||
:param nsfw: include non-suitable for work videos if True
|
||||
:type nsfw: bool
|
||||
:param max_results: maximum number of results to return
|
||||
:type max_results: int
|
||||
:rtype: :class:`BaseGallery`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -103,7 +131,8 @@ class ICapGallery(IBaseCap):
|
|||
"""
|
||||
Get gallery from an ID.
|
||||
|
||||
@param _id the gallery id. It can be a numeric ID, or a page url, or so.
|
||||
@return a Gallery object
|
||||
:param _id: the gallery id. It can be a numeric ID, or a page url, or so.
|
||||
:type _id: str
|
||||
:rtype: :class:`Gallery`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
|
|
@ -16,39 +16,42 @@
|
|||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
|
||||
from datetime import datetime
|
||||
from .base import IBaseCap, CapBaseObject
|
||||
from .base import IBaseCap, CapBaseObject, StringField, FloatField, DateField
|
||||
|
||||
|
||||
__all__ = ['ICapWaterLevel']
|
||||
__all__ = ['Gauge', 'GaugeMeasure', 'ICapWaterLevel']
|
||||
|
||||
|
||||
class Gauge(CapBaseObject):
|
||||
def __init__(self, id):
|
||||
CapBaseObject.__init__(self, id)
|
||||
|
||||
self.add_field('name', basestring)
|
||||
self.add_field('river', basestring)
|
||||
self.add_field('level', float)
|
||||
self.add_field('flow', float)
|
||||
self.add_field('lastdate', datetime)
|
||||
self.add_field('forecast', basestring)
|
||||
"""
|
||||
Gauge class.
|
||||
"""
|
||||
name = StringField('Name of gauge')
|
||||
river = StringField('What river')
|
||||
level = FloatField('Level of gauge')
|
||||
flow = FloatField('Flow of gauge')
|
||||
lastdate = DateField('Last measure')
|
||||
forecast = StringField('Forecast')
|
||||
|
||||
class GaugeMeasure(CapBaseObject):
|
||||
"""
|
||||
Measure of a gauge.
|
||||
"""
|
||||
level = FloatField('Level of measure')
|
||||
flow = FloatField('Flow of measure')
|
||||
date = DateField('Date of measure')
|
||||
|
||||
def __init__(self):
|
||||
CapBaseObject.__init__(self, None)
|
||||
|
||||
self.add_field('level', float)
|
||||
self.add_field('flow', float)
|
||||
self.add_field('date', datetime)
|
||||
|
||||
class ICapWaterLevel(IBaseCap):
|
||||
def iter_gauge_history(self, id):
|
||||
"""
|
||||
Get history of a gauge.
|
||||
|
||||
@param id [str] ID of the river
|
||||
@return [iter(GaugeMeasure)]
|
||||
:param id: ID of the river
|
||||
:type id: str
|
||||
:rtype: iter[:class:`GaugeMeasure`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -56,8 +59,9 @@ class ICapWaterLevel(IBaseCap):
|
|||
"""
|
||||
Get last measure of the gauge.
|
||||
|
||||
@param id [str] ID of the gauge.
|
||||
@return [GaugeMeasure]
|
||||
:param id: ID of the gauge
|
||||
:type id: str
|
||||
:rtype: :class:`GaugeMeasure`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -65,7 +69,8 @@ class ICapWaterLevel(IBaseCap):
|
|||
"""
|
||||
Iter gauges.
|
||||
|
||||
@param pattern [str] if specified, used to search gauges
|
||||
@return [iter(Gauge)]
|
||||
:param pattern: if specified, used to search gauges
|
||||
:type pattern: str
|
||||
:rtype: iter[:class:`Gauge`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
|
|
@ -18,27 +18,40 @@
|
|||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from .base import IBaseCap, CapBaseObject
|
||||
from .base import IBaseCap, CapBaseObject, StringField, FloatField
|
||||
|
||||
|
||||
__all__ = ['IpLocation', 'ICapGeolocIp']
|
||||
|
||||
|
||||
class IpLocation(CapBaseObject):
|
||||
"""
|
||||
Represents the location of an IP address.
|
||||
"""
|
||||
city = StringField('City')
|
||||
region = StringField('Region')
|
||||
zipcode = StringField('Zip code')
|
||||
country = StringField('Country')
|
||||
lt = FloatField('Latitude')
|
||||
lg = FloatField('Longitude')
|
||||
host = StringField('Hostname')
|
||||
tld = StringField('Top Level Domain')
|
||||
isp = StringField('Internet Service Provider')
|
||||
|
||||
def __init__(self, ipaddr):
|
||||
CapBaseObject.__init__(self, ipaddr)
|
||||
|
||||
self.ipaddr = ipaddr
|
||||
self.add_field('city', basestring)
|
||||
self.add_field('region', basestring)
|
||||
self.add_field('zipcode', basestring)
|
||||
self.add_field('country', basestring)
|
||||
self.add_field('lt', float)
|
||||
self.add_field('lg', float)
|
||||
self.add_field('host', basestring)
|
||||
self.add_field('tld', basestring)
|
||||
self.add_field('isp', basestring)
|
||||
|
||||
class ICapGeolocIp(IBaseCap):
|
||||
"""
|
||||
Access information about IP addresses database.
|
||||
"""
|
||||
def get_location(self, ipaddr):
|
||||
"""
|
||||
Get location of an IP address.
|
||||
|
||||
:param ipaddr: IP address
|
||||
:type ipaddr: str
|
||||
:rtype: :class:`IpLocation`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
|
|
@ -18,19 +18,23 @@
|
|||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from datetime import date
|
||||
|
||||
from .base import IBaseCap, CapBaseObject
|
||||
from .base import IBaseCap, CapBaseObject, Field, IntField, FloatField, \
|
||||
StringField, BytesField, DateField
|
||||
|
||||
|
||||
__all__ = ['ICapHousing']
|
||||
__all__ = ['HousingPhoto', 'Housing', 'Query', 'City', 'ICapHousing']
|
||||
|
||||
|
||||
class HousingPhoto(CapBaseObject):
|
||||
"""
|
||||
Photo of a housing.
|
||||
"""
|
||||
url = StringField('Direct URL to photo')
|
||||
data = BytesField('Data of photo')
|
||||
|
||||
def __init__(self, url):
|
||||
CapBaseObject.__init__(self, url.split('/')[-1])
|
||||
self.add_field('url', basestring, url)
|
||||
self.add_field('data', str)
|
||||
self.url = url
|
||||
|
||||
def __iscomplete__(self):
|
||||
return self.data
|
||||
|
|
@ -42,45 +46,75 @@ class HousingPhoto(CapBaseObject):
|
|||
return u'<HousingPhoto "%s" data=%do>' % (self.id, len(self.data) if self.data else 0)
|
||||
|
||||
class Housing(CapBaseObject):
|
||||
def __init__(self, id):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('title', basestring)
|
||||
self.add_field('area', (int,float))
|
||||
self.add_field('cost', (int,float))
|
||||
self.add_field('currency', basestring)
|
||||
self.add_field('date', date)
|
||||
self.add_field('location', basestring)
|
||||
self.add_field('station', basestring)
|
||||
self.add_field('text', basestring)
|
||||
self.add_field('phone', basestring)
|
||||
self.add_field('photos', list)
|
||||
self.add_field('details', dict)
|
||||
"""
|
||||
Content of a housing.
|
||||
"""
|
||||
title = StringField('Title of housing')
|
||||
area = FloatField('Area of housing, in m2')
|
||||
cost = FloatField('Cost of housing')
|
||||
currency = StringField('Currency of cost')
|
||||
date = DateField('Date when the housing has been published')
|
||||
location = StringField('Location of housing')
|
||||
station = StringField('What metro/bus station next to housing')
|
||||
text = StringField('Text of the housing')
|
||||
phone = StringField('Phone number to contact')
|
||||
photos = Field('List of photos', list)
|
||||
details = Field('Key/values of details', dict)
|
||||
|
||||
class Query(CapBaseObject):
|
||||
"""
|
||||
Query to find housings.
|
||||
"""
|
||||
TYPE_RENT = 0
|
||||
TYPE_SALE = 1
|
||||
|
||||
type = IntField('Type of housing to find (TYPE_* constants)')
|
||||
cities = Field('List of cities to search in', list, tuple)
|
||||
area_min = IntField('Minimal area (in m2)')
|
||||
area_max = IntField('Maximal area (in m2)')
|
||||
cost_min = IntField('Minimal cost')
|
||||
cost_max = IntField('Maximal cost')
|
||||
nb_rooms = IntField('Number of rooms')
|
||||
|
||||
def __init__(self):
|
||||
CapBaseObject.__init__(self, '')
|
||||
self.add_field('type', int)
|
||||
self.add_field('cities', (list,tuple))
|
||||
self.add_field('area_min', int)
|
||||
self.add_field('area_max', int)
|
||||
self.add_field('cost_min', int)
|
||||
self.add_field('cost_max', int)
|
||||
self.add_field('nb_rooms', int)
|
||||
|
||||
class City(CapBaseObject):
|
||||
def __init__(self, id):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('name', basestring)
|
||||
"""
|
||||
City.
|
||||
"""
|
||||
name = StringField('Name of city')
|
||||
|
||||
class ICapHousing(IBaseCap):
|
||||
"""
|
||||
Capability of websites to search housings.
|
||||
"""
|
||||
def search_housings(self, query):
|
||||
"""
|
||||
Search housings.
|
||||
|
||||
:param query: search query
|
||||
:type query: :class:`Query`
|
||||
:rtype: iter[:class:`Housing`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_housing(self, housing):
|
||||
"""
|
||||
Get an housing from an ID.
|
||||
|
||||
:param housing: ID of the housing
|
||||
:type housing: str
|
||||
:rtype: :class:`Housing` or None if not found.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def search_city(self, pattern):
|
||||
"""
|
||||
Search a city from a pattern.
|
||||
|
||||
:param pattern: pattern to search
|
||||
:type pattern: str
|
||||
:rtype: iter[:class:`City`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
|
|
@ -17,40 +17,59 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from datetime import datetime, date
|
||||
|
||||
from .collection import ICapCollection
|
||||
from .base import CapBaseObject
|
||||
from .base import CapBaseObject, Field, StringField, DateField
|
||||
|
||||
|
||||
__all__ = ['ICapBook', 'Book']
|
||||
__all__ = ['Book', 'Renew', 'ICapBook']
|
||||
|
||||
|
||||
class Book(CapBaseObject):
|
||||
def __init__(self, id):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('name', basestring)
|
||||
self.add_field('author', basestring)
|
||||
self.add_field('location', basestring)
|
||||
self.add_field('date', (datetime, date)) # which may be the due date
|
||||
self.add_field('late', bool)
|
||||
"""
|
||||
Describes a book.
|
||||
"""
|
||||
name = StringField('Name of the book')
|
||||
author = StringField('Author of the book')
|
||||
location = StringField('Location')
|
||||
date = DateField('The due date')
|
||||
late = Field('Are you late?', bool)
|
||||
|
||||
class Renew(CapBaseObject):
|
||||
def __init__(self, id):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('message', basestring)
|
||||
"""
|
||||
A renew message.
|
||||
"""
|
||||
message = StringField('Message')
|
||||
|
||||
class ICapBook(ICapCollection):
|
||||
"""
|
||||
Library websites.
|
||||
"""
|
||||
def iter_resources(self, objs, split_path):
|
||||
"""
|
||||
Iter resources. It retuns :func:`iter_books`.
|
||||
"""
|
||||
if Book in objs:
|
||||
self._restrict_level(split_path)
|
||||
|
||||
return self.iter_books()
|
||||
|
||||
def iter_books(self, pattern):
|
||||
"""
|
||||
Iter books from a pattern.
|
||||
|
||||
:param pattern: pattern to search
|
||||
:type pattern: str
|
||||
:rtype: iter[:class:`Book`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_book(self, _id):
|
||||
"""
|
||||
Get a book from an ID.
|
||||
|
||||
:param _id: ID of the book
|
||||
:type _id: str
|
||||
:rtype: :class:`Book`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_booked(self, _id):
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue