Commit 6cbfb7e1 authored by Vincent A's avatar Vincent A

weboob.capabilities: replace stdlib enum with custom enums

Python's enums are not extensible, values can't be added and they can't
be subclassed. This is problematic if weboob devel adds new values,
because stable weboob will not be able to use them. They would need to
be replaced by a default value or None, which loses information.

Instead, implement custom enums that contain plain constants and support
adding values aftewards.
parent 219b6aba
......@@ -138,7 +138,6 @@ def install_weboob():
'Pillow',
'mechanize; python_version < "3.0"',
'futures; python_version < "3.2"',
'enum34; python_version < "3.4"',
]
if not options.deps:
......
......@@ -174,7 +174,7 @@ class Flatboob(ReplApplication):
query.house_types.append(value)
_type = None
posts_types = sorted(POSTS_TYPES, key=lambda e: e.value)
posts_types = sorted(POSTS_TYPES)
while _type not in range(len(posts_types)):
for i, t in enumerate(posts_types):
print(' %s%2d)%s %s' % (self.BOLD,
......
......@@ -29,7 +29,7 @@ from weboob.tools.capabilities.bank.iban import is_iban_valid
from weboob.tools.compat import unicode
from .base import BaseObject, Field, StringField, DecimalField, IntField, \
UserError, Currency, NotAvailable, EnumField, IntEnum
UserError, Currency, NotAvailable, EnumField, Enum
from .date import DateField
from .collection import CapCollection
......@@ -190,7 +190,7 @@ class Recipient(BaseAccount):
iban = StringField('International Bank Account Number')
class AccountType(IntEnum):
class AccountType(Enum):
UNKNOWN = 0
CHECKING = 1
"Transaction, everyday transactions"
......@@ -312,7 +312,7 @@ class Loan(Account):
next_payment_date = DateField('Date of the next payment')
class TransactionType(IntEnum):
class TransactionType(Enum):
UNKNOWN = 0
TRANSFER = 1
ORDER = 2
......@@ -436,7 +436,7 @@ class Investment(BaseObject):
return '<Investment label=%r code=%r valuation=%r>' % (self.label, self.code, self.valuation)
class PocketCondition(IntEnum):
class PocketCondition(Enum):
UNKNOWN = 0
DATE = 1
AVAILABLE = 2
......
......@@ -18,7 +18,6 @@
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
from collections import OrderedDict
from enum import Enum as _Enum
import warnings
import re
from decimal import Decimal
......@@ -32,27 +31,65 @@ from weboob.tools.misc import to_unicode
__all__ = ['UserError', 'FieldNotFound', 'NotAvailable', 'FetchError',
'NotLoaded', 'Capability', 'Field', 'IntField', 'DecimalField',
'FloatField', 'StringField', 'BytesField', 'BoolField',
'Enum', 'IntEnum', 'StrEnum', 'EnumField',
'Enum', 'EnumField',
'empty', 'BaseObject']
class Enum(_Enum):
pass
class EnumMeta(type):
@classmethod
def __prepare__(mcs, name, bases, **kwargs):
# in python3.6, default namespace keeps declaration order
# in python>=3 but <3.6, force ordered namespace
# doesn't work in python2
return OrderedDict()
def __init__(cls, name, bases, attrs, *args, **kwargs):
super(EnumMeta, cls).__init__(name, bases, attrs, *args, **kwargs)
attrs = [(k, v) for k, v in attrs.items() if not callable(v) and not k.startswith('__')]
if sys.version_info.major < 3:
# can't have original declaration order, at least sort by value
attrs.sort(key=lambda kv: kv[1])
cls.__members__ = OrderedDict(attrs)
def __setattr__(cls, name, value):
super(EnumMeta, cls).__setattr__(name, value)
if not callable(value) and not name.startswith('__'):
cls.__members__[name] = value
def __call__(cls, *args, **kwargs):
raise ValueError("Enum type can't be instanciated")
@property
def _items(cls):
return cls.__members__.items()
@property
def _keys(cls):
return cls.__members__.keys()
@property
def _values(cls):
return cls.__members__.values()
@property
def _types(cls):
return set(map(type, cls._values))
def __iter__(cls):
return iter(cls.__members__.values())
class StrEnum(unicode, Enum):
if sys.version_info.major < 3:
# cannot use StrConv helper, else for some reason it will error like:
# TypeError: <AccountType.TYPE_UNKNOWN: 0> cannot be pickled
def __str__(self):
return unicode(self.value).encode('utf-8')
else:
def __str__(self):
return str(self.value)
def __len__(cls):
return len(cls.__members__)
def __contains__(cls, value):
return value in cls.__members__.values()
class IntEnum(int, Enum):
__str__ = int.__str__
def __getitem__(cls, k):
return cls.__members__[k]
class Enum(with_metaclass(EnumMeta, object)):
pass
def empty(value):
......@@ -306,13 +343,15 @@ class BytesField(Field):
class EnumField(Field):
def __init__(self, doc, enum, **kwargs):
super(EnumField, self).__init__(doc, enum, **kwargs)
if not issubclass(enum, _Enum):
if not issubclass(enum, Enum):
raise TypeError('invalid enum type: %r' % enum)
super(EnumField, self).__init__(doc, *enum._types, **kwargs)
self.enum = enum
def convert(self, value):
return self.enum(value)
if value not in self.enum._values:
raise ValueError('value %r does not belong to enum %s' % (value, self.enum))
return value
class _BaseObjectMeta(type):
......
......@@ -20,7 +20,7 @@
from weboob.tools.compat import unicode
from .base import Capability, BaseObject, Field, StringField,\
IntField, UserError, IntEnum
IntField, UserError, Enum
from .date import DateField, DeltaField
......@@ -135,7 +135,7 @@ class Version(BaseObject):
return '<Version %r>' % self.name
class StatusType(IntEnum):
class StatusType(Enum):
NEW = 0
PROGRESS = 1
RESOLVED = 2
......
......@@ -19,7 +19,7 @@
from .base import (
BaseObject, StringField, IntField, FloatField, Field, EnumField,
StrEnum,
Enum,
)
from .collection import CapCollection, CollectionNotFound, Collection
from .date import DateField
......@@ -30,7 +30,7 @@ from weboob.tools.date import parse_date
__all__ = ['BaseCalendarEvent', 'CapCalendarEvent']
class CATEGORIES(StrEnum):
class CATEGORIES(Enum):
CONCERT = u'Concert'
CINE = u'Cinema'
THEATRE = u'Theatre'
......@@ -45,18 +45,18 @@ class CATEGORIES(StrEnum):
#the following elements deal with ICalendar stantdards
#see http://fr.wikipedia.org/wiki/ICalendar#Ev.C3.A9nements_.28VEVENT.29
class TRANSP(StrEnum):
class TRANSP(Enum):
OPAQUE = u'OPAQUE'
TRANSPARENT = u'TRANSPARENT'
class STATUS(StrEnum):
class STATUS(Enum):
TENTATIVE = u'TENTATIVE'
CONFIRMED = u'CONFIRMED'
CANCELLED = u'CANCELLED'
class TICKET(StrEnum):
class TICKET(Enum):
AVAILABLE = u'Available'
NOTAVAILABLE = u'Not available'
CLOSED = u'Closed'
......
......@@ -19,14 +19,14 @@
from weboob.tools.compat import long
from .base import Capability, BaseObject, NotAvailable, Field, StringField, IntField, StrEnum, IntEnum
from .base import Capability, BaseObject, NotAvailable, Field, StringField, IntField, Enum
from .date import DateField
__all__ = ['BaseFile', 'CapFile']
class LICENSES(StrEnum):
class LICENSES(Enum):
OTHER = u'Other license'
PD = u'Public Domain'
COPYRIGHT = u'All rights reserved'
......@@ -74,7 +74,7 @@ class BaseFile(BaseObject):
return self.id2url(self.id)
class SearchSort(IntEnum):
class SearchSort(Enum):
RELEVANCE = 0
RATING = 1
VIEWS = 2
......
......@@ -21,7 +21,7 @@
from weboob.capabilities.image import BaseImage as CIBaseImage, Thumbnail
from weboob.tools.compat import unicode
from .base import Capability, BaseObject, NotLoaded, Field, StringField, \
IntField, FloatField, IntEnum
IntField, FloatField, Enum
from .date import DateField
......@@ -98,7 +98,7 @@ class BaseImage(CIBaseImage):
return self.data is not NotLoaded
class SearchSort(IntEnum):
class SearchSort(Enum):
RELEVANCE = 0
RATING = 1
VIEWS = 2
......
......@@ -19,7 +19,7 @@
from .base import Capability, BaseObject, Field, IntField, DecimalField, \
StringField, BytesField, StrEnum, EnumField, UserError
StringField, BytesField, Enum, EnumField, UserError
from .date import DateField
__all__ = [
......@@ -58,13 +58,13 @@ class HousingPhoto(BaseObject):
return '<HousingPhoto %r data=%do url=%r>' % (self.id, len(self.data) if self.data else 0, self.url)
class UTILITIES(StrEnum):
class UTILITIES(Enum):
INCLUDED = u'C.C.'
EXCLUDED = u'H.C.'
UNKNOWN = u''
class ENERGY_CLASS(StrEnum):
class ENERGY_CLASS(Enum):
A = u'A'
B = u'B'
C = u'C'
......@@ -74,7 +74,7 @@ class ENERGY_CLASS(StrEnum):
G = u'G'
class POSTS_TYPES(StrEnum):
class POSTS_TYPES(Enum):
RENT=u'RENT'
SALE = u'SALE'
SHARING = u'SHARING'
......@@ -82,12 +82,12 @@ class POSTS_TYPES(StrEnum):
VIAGER = u'VIAGER'
class ADVERT_TYPES(StrEnum):
class ADVERT_TYPES(Enum):
PROFESSIONAL = u'Professional'
PERSONAL = u'Personal'
class HOUSE_TYPES(StrEnum):
class HOUSE_TYPES(Enum):
APART = u'Apartment'
HOUSE = u'House'
PARKING = u'Parking'
......
......@@ -18,7 +18,7 @@
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
from .base import Capability, BaseObject, Field, StringField, UserError, IntEnum
from .base import Capability, BaseObject, Field, StringField, UserError, Enum
from .date import DateField
......@@ -31,7 +31,7 @@ class Event(BaseObject):
return '<Event date=%r activity=%r location=%r>' % (self.date, self.activity, self.location)
class ParcelState(IntEnum):
class ParcelState(Enum):
UNKNOWN = 0
PLANNED = 1
IN_TRANSIT = 2
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment