change way to describe fields of CapBaseObject, and lot of documentation

This commit is contained in:
Romain Bignon 2012-03-25 22:29:18 +02:00
commit c6a141595c
35 changed files with 1630 additions and 638 deletions

View file

@ -1,5 +0,0 @@
Applications
============
boobank
-------

View file

@ -1,5 +0,0 @@
Backends
========
cragr
-----

View file

@ -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)

View file

@ -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

View file

@ -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``

View file

@ -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
------------

View file

@ -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()

View file

@ -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()

View file

@ -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

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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