[IMP] openerp/osv/fields: add `__slots__` on all column classes

Use the same technique as for field classes.
This saves about 1.6Mb of memory per registry.
This commit is contained in:
Raphael Collet 2015-03-11 14:05:39 +01:00
parent e7928e1265
commit 4259fe0072
1 changed files with 161 additions and 86 deletions

View File

@ -74,7 +74,6 @@ class _column(object):
_classic_read = True _classic_read = True
_classic_write = True _classic_write = True
_auto_join = False _auto_join = False
_prefetch = True
_properties = False _properties = False
_type = 'unknown' _type = 'unknown'
_obj = None _obj = None
@ -85,59 +84,64 @@ class _column(object):
_symbol_get = None _symbol_get = None
_deprecated = False _deprecated = False
copy = True # whether value is copied by BaseModel.copy() __slots__ = [
string = None 'copy', # whether value is copied by BaseModel.copy()
help = "" 'string',
required = False 'help',
readonly = False 'required',
_domain = [] 'readonly',
_context = {} '_domain',
states = None '_context',
priority = 0 'states',
change_default = False 'priority',
size = None 'change_default',
ondelete = None 'size',
translate = False 'ondelete',
select = False 'translate',
manual = False 'select',
write = False 'manual',
read = False 'write',
selectable = True 'read',
group_operator = False 'selectable',
groups = False # CSV list of ext IDs of groups 'group_operator',
deprecated = False # Optional deprecation warning 'groups', # CSV list of ext IDs of groups
'deprecated', # Optional deprecation warning
'_args',
'_prefetch',
]
def __init__(self, string='unknown', required=False, readonly=False, domain=None, context=None, states=None, priority=0, change_default=False, size=None, ondelete=None, translate=False, select=False, manual=False, **args): def __init__(self, string='unknown', required=False, readonly=False, domain=[], context={}, states=None, priority=0, change_default=False, size=None, ondelete=None, translate=False, select=False, manual=False, **args):
""" """
The 'manual' keyword argument specifies if the field is a custom one. The 'manual' keyword argument specifies if the field is a custom one.
It corresponds to the 'state' column in ir_model_fields. It corresponds to the 'state' column in ir_model_fields.
""" """
args0 = { # add parameters and default values
'string': string, args['copy'] = args.get('copy', True)
'help': args.pop('help', None), args['string'] = string
'required': required, args['help'] = args.get('help', '')
'readonly': readonly, args['required'] = required
'_domain': domain, args['readonly'] = readonly
'_context': context, args['_domain'] = domain
'states': states, args['_context'] = context
'priority': priority, args['states'] = states
'change_default': change_default, args['priority'] = priority
'size': size, args['change_default'] = change_default
'ondelete': ondelete.lower() if ondelete else None, args['size'] = size
'translate': translate, args['ondelete'] = ondelete.lower() if ondelete else None
'select': select, args['translate'] = translate
'manual': manual, args['select'] = select
'group_operator': args.pop('group_operator', None), args['manual'] = manual
'groups': args.pop('groups', None), args['write'] = args.get('write', False)
'deprecated': args.pop('deprecated', None), args['read'] = args.get('read', False)
} args['selectable'] = args.get('selectable', True)
for key, val in args0.iteritems(): args['group_operator'] = args.get('group_operator', None)
if val: args['groups'] = args.get('groups', None)
setattr(self, key, val) args['deprecated'] = args.get('deprecated', None)
args['_prefetch'] = args.get('_prefetch', True)
self._args = args or EMPTY_DICT self._args = EMPTY_DICT
for key, val in args.iteritems(): for key, val in args.iteritems():
setattr(self, key, val) setattr(self, key, val)
@ -145,6 +149,32 @@ class _column(object):
if not self._classic_write or self.deprecated or self.manual: if not self._classic_write or self.deprecated or self.manual:
self._prefetch = False self._prefetch = False
def __getattr__(self, name):
""" Access a non-slot attribute. """
if name == '_args':
raise AttributeError(name)
try:
return self._args[name]
except KeyError:
raise AttributeError(name)
def __setattr__(self, name, value):
""" Set a slot or non-slot attribute. """
try:
object.__setattr__(self, name, value)
except AttributeError:
if self._args:
self._args[name] = value
else:
self._args = {name: value} # replace EMPTY_DICT
def __delattr__(self, name):
""" Remove a non-slot attribute. """
try:
del self._args[name]
except KeyError:
raise AttributeError(name)
def new(self, _computed_field=False, **args): def new(self, _computed_field=False, **args):
""" Return a column like `self` with the given parameters; the parameter """ Return a column like `self` with the given parameters; the parameter
`_computed_field` tells whether the corresponding field is computed. `_computed_field` tells whether the corresponding field is computed.
@ -224,6 +254,7 @@ class boolean(_column):
_symbol_c = '%s' _symbol_c = '%s'
_symbol_f = bool _symbol_f = bool
_symbol_set = (_symbol_c, _symbol_f) _symbol_set = (_symbol_c, _symbol_f)
__slots__ = []
def __init__(self, string='unknown', required=False, **args): def __init__(self, string='unknown', required=False, **args):
super(boolean, self).__init__(string=string, required=required, **args) super(boolean, self).__init__(string=string, required=required, **args)
@ -239,6 +270,7 @@ class integer(_column):
_symbol_f = lambda x: int(x or 0) _symbol_f = lambda x: int(x or 0)
_symbol_set = (_symbol_c, _symbol_f) _symbol_set = (_symbol_c, _symbol_f)
_symbol_get = lambda self,x: x or 0 _symbol_get = lambda self,x: x or 0
__slots__ = []
def __init__(self, string='unknown', required=False, **args): def __init__(self, string='unknown', required=False, **args):
super(integer, self).__init__(string=string, required=required, **args) super(integer, self).__init__(string=string, required=required, **args)
@ -246,6 +278,7 @@ class integer(_column):
class reference(_column): class reference(_column):
_type = 'reference' _type = 'reference'
_classic_read = False # post-process to handle missing target _classic_read = False # post-process to handle missing target
__slots__ = ['selection']
def __init__(self, string, selection, size=None, **args): def __init__(self, string, selection, size=None, **args):
if callable(selection): if callable(selection):
@ -298,6 +331,7 @@ def _symbol_set_char(self, symb):
class char(_column): class char(_column):
_type = 'char' _type = 'char'
__slots__ = ['_symbol_f', '_symbol_set', '_symbol_set_char']
def __init__(self, string="unknown", size=None, **args): def __init__(self, string="unknown", size=None, **args):
_column.__init__(self, string=string, size=size or None, **args) _column.__init__(self, string=string, size=size or None, **args)
@ -307,11 +341,13 @@ class char(_column):
class text(_column): class text(_column):
_type = 'text' _type = 'text'
__slots__ = []
class html(text): class html(text):
_type = 'html' _type = 'html'
_symbol_c = '%s' _symbol_c = '%s'
__slots__ = ['_sanitize', '_strip_style', '_symbol_f', '_symbol_set']
def _symbol_set_html(self, value): def _symbol_set_html(self, value):
if value is None or value is False: if value is None or value is False:
@ -347,6 +383,7 @@ class float(_column):
_type = 'float' _type = 'float'
_symbol_c = '%s' _symbol_c = '%s'
_symbol_get = lambda self,x: x or 0.0 _symbol_get = lambda self,x: x or 0.0
__slots__ = ['_digits', '_digits_compute', '_symbol_f', '_symbol_set']
@property @property
def digits(self): def digits(self):
@ -374,6 +411,7 @@ class float(_column):
class date(_column): class date(_column):
_type = 'date' _type = 'date'
__slots__ = []
MONTHS = [ MONTHS = [
('01', 'January'), ('01', 'January'),
@ -464,6 +502,7 @@ class date(_column):
class datetime(_column): class datetime(_column):
_type = 'datetime' _type = 'datetime'
__slots__ = []
MONTHS = [ MONTHS = [
('01', 'January'), ('01', 'January'),
@ -533,7 +572,7 @@ class datetime(_column):
class binary(_column): class binary(_column):
_type = 'binary' _type = 'binary'
_symbol_c = '%s' _classic_read = False
# Binary values may be byte strings (python 2.6 byte array), but # Binary values may be byte strings (python 2.6 byte array), but
# the legacy OpenERP convention is to transfer and store binaries # the legacy OpenERP convention is to transfer and store binaries
@ -541,17 +580,16 @@ class binary(_column):
# unicode in some circumstances, hence the str() cast in symbol_f. # unicode in some circumstances, hence the str() cast in symbol_f.
# This str coercion will only work for pure ASCII unicode strings, # This str coercion will only work for pure ASCII unicode strings,
# on purpose - non base64 data must be passed as a 8bit byte strings. # on purpose - non base64 data must be passed as a 8bit byte strings.
_symbol_c = '%s'
_symbol_f = lambda symb: symb and Binary(str(symb)) or None _symbol_f = lambda symb: symb and Binary(str(symb)) or None
_symbol_set = (_symbol_c, _symbol_f) _symbol_set = (_symbol_c, _symbol_f)
_symbol_get = lambda self, x: x and str(x) _symbol_get = lambda self, x: x and str(x)
_classic_read = False __slots__ = ['filters']
_prefetch = False
def __init__(self, string='unknown', filters=None, **args): def __init__(self, string='unknown', filters=None, **args):
_column.__init__(self, string=string, **args) args['_prefetch'] = args.get('_prefetch', False)
self.filters = filters _column.__init__(self, string=string, filters=filters, **args)
def get(self, cr, obj, ids, name, user=None, context=None, values=None): def get(self, cr, obj, ids, name, user=None, context=None, values=None):
if not context: if not context:
@ -579,13 +617,13 @@ class binary(_column):
class selection(_column): class selection(_column):
_type = 'selection' _type = 'selection'
__slots__ = ['selection']
def __init__(self, selection, string='unknown', **args): def __init__(self, selection, string='unknown', **args):
if callable(selection): if callable(selection):
from openerp import api from openerp import api
selection = api.expected(api.cr_uid_context, selection) selection = api.expected(api.cr_uid_context, selection)
_column.__init__(self, string=string, **args) _column.__init__(self, string=string, selection=selection, **args)
self.selection = selection
def to_field_args(self): def to_field_args(self):
args = super(selection, self).to_field_args() args = super(selection, self).to_field_args()
@ -646,9 +684,10 @@ class many2one(_column):
_symbol_f = lambda x: x or None _symbol_f = lambda x: x or None
_symbol_set = (_symbol_c, _symbol_f) _symbol_set = (_symbol_c, _symbol_f)
ondelete = 'set null' __slots__ = ['ondelete', '_obj', '_auto_join']
def __init__(self, obj, string='unknown', auto_join=False, **args): def __init__(self, obj, string='unknown', auto_join=False, **args):
args['ondelete'] = args.get('ondelete', 'set null')
_column.__init__(self, string=string, **args) _column.__init__(self, string=string, **args)
self._obj = obj self._obj = obj
self._auto_join = auto_join self._auto_join = auto_join
@ -694,13 +733,14 @@ class many2one(_column):
class one2many(_column): class one2many(_column):
_classic_read = False _classic_read = False
_classic_write = False _classic_write = False
_prefetch = False
_type = 'one2many' _type = 'one2many'
# one2many columns are not copied by default __slots__ = ['_obj', '_fields_id', '_limit', '_auto_join']
copy = False
def __init__(self, obj, fields_id, string='unknown', limit=None, auto_join=False, **args): def __init__(self, obj, fields_id, string='unknown', limit=None, auto_join=False, **args):
# one2many columns are not copied by default
args['copy'] = args.get('copy', False)
args['_prefetch'] = args.get('_prefetch', False)
_column.__init__(self, string=string, **args) _column.__init__(self, string=string, **args)
self._obj = obj self._obj = obj
self._fields_id = fields_id self._fields_id = fields_id
@ -841,12 +881,14 @@ class many2many(_column):
""" """
_classic_read = False _classic_read = False
_classic_write = False _classic_write = False
_prefetch = False
_type = 'many2many' _type = 'many2many'
__slots__ = ['_obj', '_rel', '_id1', '_id2', '_limit', '_auto_join']
def __init__(self, obj, rel=None, id1=None, id2=None, string='unknown', limit=None, **args): def __init__(self, obj, rel=None, id1=None, id2=None, string='unknown', limit=None, **args):
""" """
""" """
args['_prefetch'] = args.get('_prefetch', False)
_column.__init__(self, string=string, **args) _column.__init__(self, string=string, **args)
self._obj = obj self._obj = obj
if rel and '.' in rel: if rel and '.' in rel:
@ -856,6 +898,7 @@ class many2many(_column):
self._id1 = id1 self._id1 = id1
self._id2 = id2 self._id2 = id2
self._limit = limit self._limit = limit
self._auto_join = False
def to_field_args(self): def to_field_args(self):
args = super(many2many, self).to_field_args() args = super(many2many, self).to_field_args()
@ -1238,14 +1281,30 @@ class function(_column):
} }
""" """
_classic_read = False
_classic_write = False
_prefetch = False
_type = 'function'
_properties = True _properties = True
# function fields are not copied by default __slots__ = [
copy = False '_type',
'_classic_read',
'_classic_write',
'_symbol_c',
'_symbol_f',
'_symbol_set',
'_symbol_get',
'_fnct',
'_arg',
'_fnct_inv',
'_fnct_inv_arg',
'_fnct_search',
'_multi',
'store',
'_digits',
'_digits_compute',
'selection',
'_obj',
]
@property @property
def digits(self): def digits(self):
@ -1259,33 +1318,43 @@ class function(_column):
# multi: compute several fields in one call # multi: compute several fields in one call
# #
def __init__(self, fnct, arg=None, fnct_inv=None, fnct_inv_arg=None, type='float', fnct_search=None, obj=None, store=False, multi=False, **args): def __init__(self, fnct, arg=None, fnct_inv=None, fnct_inv_arg=None, type='float', fnct_search=None, obj=None, store=False, multi=False, **args):
self._classic_read = False
self._classic_write = False
self._prefetch = False
self._symbol_c = '%s'
self._symbol_f = _symbol_set
self._symbol_set = (self._symbol_c, self._symbol_f)
self._symbol_get = None
# pop attributes that should not be assigned to self # pop attributes that should not be assigned to self
self._digits = args.pop('digits', (16,2)) self._digits = args.pop('digits', (16,2))
self._digits_compute = args.pop('digits_compute', None) self._digits_compute = args.pop('digits_compute', None)
self._obj = args.pop('relation', obj)
# function fields are not copied by default
args['copy'] = args.get('copy', False)
_column.__init__(self, **args) _column.__init__(self, **args)
self._obj = obj
self._type = type
self._fnct = fnct self._fnct = fnct
self._fnct_inv = fnct_inv
self._arg = arg self._arg = arg
self._fnct_inv = fnct_inv
self._fnct_inv_arg = fnct_inv_arg
self._fnct_search = fnct_search
self.store = store
self._multi = multi self._multi = multi
if 'relation' in args:
self._obj = args['relation'] if not fnct_inv:
self.readonly = 1
if not fnct_search and not store:
self.selectable = False
if callable(args.get('selection')): if callable(args.get('selection')):
from openerp import api from openerp import api
self.selection = api.expected(api.cr_uid_context, args['selection']) self.selection = api.expected(api.cr_uid_context, args['selection'])
self._fnct_inv_arg = fnct_inv_arg
if not fnct_inv:
self.readonly = 1
self._type = type
self._fnct_search = fnct_search
self.store = store
if not fnct_search and not store:
self.selectable = False
if store: if store:
if self._type != 'many2one': if self._type != 'many2one':
# m2o fields need to return tuples with name_get, not just foreign keys # m2o fields need to return tuples with name_get, not just foreign keys
@ -1421,6 +1490,7 @@ class related(function):
'bar': fields.related('foo_id', 'frol', type='char', string='Frol of Foo'), 'bar': fields.related('foo_id', 'frol', type='char', string='Frol of Foo'),
} }
""" """
__slots__ = ['arg', '_relations']
def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None): def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
# assume self._arg = ('foo', 'bar', 'baz') # assume self._arg = ('foo', 'bar', 'baz')
@ -1472,7 +1542,8 @@ class related(function):
pass pass
class sparse(function): class sparse(function):
__slots__ = ['serialization_field']
def convert_value(self, obj, cr, uid, record, value, read_value, context=None): def convert_value(self, obj, cr, uid, record, value, read_value, context=None):
""" """
@ -1521,7 +1592,6 @@ class sparse(function):
return read_value return read_value
return value return value
def _fnct_write(self,obj,cr, uid, ids, field_name, value, args, context=None): def _fnct_write(self,obj,cr, uid, ids, field_name, value, args, context=None):
if not type(ids) == list: if not type(ids) == list:
ids = [ids] ids = [ids]
@ -1564,7 +1634,6 @@ class sparse(function):
def __init__(self, serialization_field, **kwargs): def __init__(self, serialization_field, **kwargs):
self.serialization_field = serialization_field self.serialization_field = serialization_field
super(sparse, self).__init__(self._fnct_read, fnct_inv=self._fnct_write, multi='__sparse_multi', **kwargs) super(sparse, self).__init__(self._fnct_read, fnct_inv=self._fnct_write, multi='__sparse_multi', **kwargs)
# --------------------------------------------------------- # ---------------------------------------------------------
@ -1572,6 +1641,8 @@ class sparse(function):
# --------------------------------------------------------- # ---------------------------------------------------------
class dummy(function): class dummy(function):
__slots__ = ['arg', '_relations']
def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None): def _fnct_search(self, tobj, cr, uid, obj=None, name=None, domain=None, context=None):
return [] return []
@ -1595,23 +1666,27 @@ class serialized(_column):
Note: only plain components allowed. Note: only plain components allowed.
""" """
_type = 'serialized'
__slots__ = []
def _symbol_set_struct(val): def _symbol_set_struct(val):
return simplejson.dumps(val) return simplejson.dumps(val)
def _symbol_get_struct(self, val): def _symbol_get_struct(self, val):
return simplejson.loads(val or '{}') return simplejson.loads(val or '{}')
_prefetch = False
_type = 'serialized'
_symbol_c = '%s' _symbol_c = '%s'
_symbol_f = _symbol_set_struct _symbol_f = _symbol_set_struct
_symbol_set = (_symbol_c, _symbol_f) _symbol_set = (_symbol_c, _symbol_f)
_symbol_get = _symbol_get_struct _symbol_get = _symbol_get_struct
def __init__(self, *args, **kwargs):
kwargs['_prefetch'] = kwargs.get('_prefetch', False)
super(serialized, self).__init__(*args, **kwargs)
# TODO: review completly this class for speed improvement # TODO: review completly this class for speed improvement
class property(function): class property(function):
__slots__ = []
def to_field_args(self): def to_field_args(self):
args = super(property, self).to_field_args() args = super(property, self).to_field_args()