diff options
| author | Raphael Collet <rco@odoo.com> | 2020-04-02 06:45:54 +0000 |
|---|---|---|
| committer | Raphael Collet <rco@odoo.com> | 2020-04-03 11:40:27 +0000 |
| commit | f3932ea71641773a2d2a0b123494edff9cfe0ac0 (patch) | |
| tree | 2e25eff3a7ddd2b9e671258d2f5fbb450e4f58aa /odoo/fields.py | |
| parent | f694caac416cd66c3878cf6352855cb9765c3b45 (diff) | |
[IMP] core: remove `Field._slots`
This optimization is no longer necessary. Thanks to Python 3.6's new
implementation of dicts, the memory footprint difference between slots
and dicts is now around 5%, which is no longer worth the complexity and
performance cost.
This saves 2% of the total installation time.
X-original-commit: c7f17770803744cbbb741fc11265ee914641aed7
Diffstat (limited to 'odoo/fields.py')
| -rw-r--r-- | odoo/fields.py | 287 |
1 files changed, 109 insertions, 178 deletions
diff --git a/odoo/fields.py b/odoo/fields.py index 2420f1090b5..ac8151e183b 100644 --- a/odoo/fields.py +++ b/odoo/fields.py @@ -15,7 +15,7 @@ import pytz import psycopg2 from .tools import ( - float_repr, float_round, float_compare, float_is_zero, frozendict, html_sanitize, human_size, + float_repr, float_round, float_compare, float_is_zero, html_sanitize, human_size, pg_varchar, ustr, OrderedSet, pycompat, sql, date_utils, unique, IterableGenerator, image_process, merge_sequences, ) @@ -28,7 +28,6 @@ from odoo.exceptions import CacheMiss DATE_LENGTH = len(date.today().strftime(DATE_FORMAT)) DATETIME_LENGTH = len(datetime.now().strftime(DATETIME_FORMAT)) -EMPTY_DICT = frozendict() RENAMED_ATTRS = [('select', 'index'), ('digits_compute', 'digits')] DEPRECATED_ATTRS = [("oldname", "use an upgrade script instead.")] @@ -69,21 +68,6 @@ class MetaField(type): """ Metaclass for field classes. """ by_type = {} - def __new__(meta, name, bases, attrs): - """ Combine the ``_slots`` dict from parent classes, and determine - ``__slots__`` for them on the new class. - """ - base_slots = {} - for base in reversed(bases): - base_slots.update(getattr(base, '_slots', ())) - - slots = dict(base_slots) - slots.update(attrs.get('_slots', ())) - - attrs['__slots__'] = set(slots) - set(base_slots) - attrs['_slots'] = slots - return type.__new__(meta, name, bases, attrs) - def __init__(cls, name, bases, attrs): super(MetaField, cls).__init__(name, bases, attrs) if not hasattr(cls, 'type'): @@ -101,7 +85,10 @@ class MetaField(type): elif attr.startswith('_description_'): cls.description_attrs.append((attr[13:], attr)) + _global_seq = iter(itertools.count()) + + class Field(MetaField('DummyField', (object,), {})): """The field descriptor contains the field definition, and manages accesses and assignments of the corresponding field on records. The following @@ -215,96 +202,58 @@ class Field(MetaField('DummyField', (object,), {})): column_format = '%s' # placeholder for value in queries column_cast_from = () # column types that may be cast to this - _slots = { - 'args': EMPTY_DICT, # the parameters given to __init__() - '_attrs': EMPTY_DICT, # the field's non-slot attributes - '_module': None, # the field's module name - '_modules': None, # modules that define this field - '_setup_done': None, # the field's setup state: None, 'base' or 'full' - '_sequence': None, # absolute ordering of the field - - 'automatic': False, # whether the field is automatically created ("magic" field) - 'inherited': False, # whether the field is inherited (_inherits) - 'inherited_field': None, # the corresponding inherited field - - 'name': None, # name of the field - 'model_name': None, # name of the model of this field - 'comodel_name': None, # name of the model of values (if relational) - - 'store': True, # whether the field is stored in database - 'index': False, # whether the field is indexed in database - 'manual': False, # whether the field is a custom field - 'copy': True, # whether the field is copied over by BaseModel.copy() - 'depends': None, # collection of field dependencies - 'depends_context': None, # collection of context key dependencies - 'recursive': False, # whether self depends on itself - 'compute': None, # compute(recs) computes field on recs - 'compute_sudo': False, # whether field should be recomputed as superuser - 'inverse': None, # inverse(recs) inverses field on recs - 'search': None, # search(recs, operator, value) searches on self - 'related': None, # sequence of field names, for related fields - 'company_dependent': False, # whether ``self`` is company-dependent (property field) - 'default': None, # default(recs) returns the default value - - 'string': None, # field label - 'help': None, # field tooltip - 'readonly': False, # whether the field is readonly - 'required': False, # whether the field is required - 'states': None, # set readonly and required depending on state - 'groups': None, # csv list of group xml ids - 'change_default': False, # whether the field may trigger a "user-onchange" - 'deprecated': None, # whether the field is deprecated - - 'related_field': None, # corresponding related field - 'group_operator': None, # operator for aggregating values - 'group_expand': None, # name of method to expand groups in read_group() - 'prefetch': True, # whether the field is prefetched - } + args = None # the parameters given to __init__() + _module = None # the field's module name + _modules = None # modules that define this field + _setup_done = None # the field's setup state: None, 'base' or 'full' + _sequence = None # absolute ordering of the field + + automatic = False # whether the field is automatically created ("magic" field) + inherited = False # whether the field is inherited (_inherits) + inherited_field = None # the corresponding inherited field + + name = None # name of the field + model_name = None # name of the model of this field + comodel_name = None # name of the model of values (if relational) + + store = True # whether the field is stored in database + index = False # whether the field is indexed in database + manual = False # whether the field is a custom field + copy = True # whether the field is copied over by BaseModel.copy() + depends = None # collection of field dependencies + depends_context = None # collection of context key dependencies + recursive = False # whether self depends on itself + compute = None # compute(recs) computes field on recs + compute_sudo = False # whether field should be recomputed as superuser + inverse = None # inverse(recs) inverses field on recs + search = None # search(recs, operator, value) searches on self + related = None # sequence of field names, for related fields + company_dependent = False # whether ``self`` is company-dependent (property field) + default = None # default(recs) returns the default value + + string = None # field label + help = None # field tooltip + readonly = False # whether the field is readonly + required = False # whether the field is required + states = None # set readonly and required depending on state + groups = None # csv list of group xml ids + change_default = False # whether the field may trigger a "user-onchange" + deprecated = None # whether the field is deprecated + + related_field = None # corresponding related field + group_operator = None # operator for aggregating values + group_expand = None # name of method to expand groups in read_group() + prefetch = True # whether the field is prefetched def __init__(self, string=Default, **kwargs): kwargs['string'] = string self._sequence = kwargs['_sequence'] = next(_global_seq) - args = {key: val for key, val in kwargs.items() if val is not Default} - self.args = args or EMPTY_DICT - self._setup_done = None + self.args = {key: val for key, val in kwargs.items() if val is not Default} def new(self, **kwargs): """ Return a field of the same type as ``self``, with its own parameters. """ return type(self)(**kwargs) - def __getattr__(self, name): - """ Access non-slot field attribute. """ - try: - return self._attrs[name] - except KeyError: - raise AttributeError(name) - - def __setattr__(self, name, value): - """ Set slot or non-slot field attribute. """ - try: - object.__setattr__(self, name, value) - except AttributeError: - if self._attrs: - self._attrs[name] = value - else: - self._attrs = {name: value} # replace EMPTY_DICT - - def set_all_attrs(self, attrs): - """ Set all field attributes at once (with slot defaults). """ - # optimization: we assign slots only - assign = object.__setattr__ - for key, val in self._slots.items(): - assign(self, key, attrs.pop(key, val)) - if attrs: - assign(self, '_attrs', attrs) - - def __delattr__(self, name): - """ Remove non-slot field attribute. """ - try: - del self._attrs[name] - except KeyError: - raise AttributeError(name) - def __str__(self): return "%s.%s" % (self.model_name, self.name) @@ -397,7 +346,7 @@ class Field(MetaField('DummyField', (object,), {})): def _setup_attrs(self, model, name): """ Initialize the field parameter attributes. """ attrs = self._get_attrs(model, name) - self.set_all_attrs(attrs) + self.__dict__.update(attrs) # check for renamed attributes (conversion errors) for key1, key2 in RENAMED_ATTRS: @@ -515,8 +464,8 @@ class Field(MetaField('DummyField', (object,), {})): if not getattr(self, attr): setattr(self, attr, getattr(field, prop)) - for attr, value in field._attrs.items(): - if attr not in self._attrs: + for attr, value in field.__dict__.items(): + if not hasattr(self, attr): setattr(self, attr, value) # special case for states: copy it only for inherited fields @@ -1175,9 +1124,8 @@ class Boolean(Field): class Integer(Field): type = 'integer' column_type = ('int4', 'int4') - _slots = { - 'group_operator': 'sum', - } + + group_operator = 'sum' def convert_to_column(self, value, record, values=None, validate=True): return int(value or 0) @@ -1250,10 +1198,9 @@ class Float(Field): type = 'float' column_cast_from = ('int4', 'numeric', 'float8') - _slots = { - '_digits': None, # digits argument passed to class initializer - 'group_operator': 'sum', - } + + _digits = None # digits argument passed to class initializer + group_operator = 'sum' def __init__(self, string=Default, digits=Default, **kwargs): super(Float, self).__init__(string=string, _digits=digits, **kwargs) @@ -1318,10 +1265,9 @@ class Monetary(Field): type = 'monetary' column_type = ('numeric', 'numeric') column_cast_from = ('float8',) - _slots = { - 'currency_field': None, - 'group_operator': 'sum', - } + + currency_field = None + group_operator = 'sum' def __init__(self, string=Default, currency_field=Default, **kwargs): super(Monetary, self).__init__(string=string, currency_field=currency_field, **kwargs) @@ -1389,10 +1335,8 @@ class Monetary(Field): class _String(Field): """ Abstract class for string fields. """ - _slots = { - 'translate': False, # whether the field is translated - 'prefetch': None, - } + translate = False # whether the field is translated + prefetch = None def __init__(self, string=Default, **kwargs): # translate is either True, False, or a callable @@ -1553,10 +1497,9 @@ class Char(_String): """ type = 'char' column_cast_from = ('text',) - _slots = { - 'size': None, # maximum size of values (deprecated) - 'trim': True, # whether value is trimmed (only by web client) - } + + size = None # maximum size of values (deprecated) + trim = True # whether value is trimmed (only by web client) @property def column_type(self): @@ -1618,15 +1561,14 @@ class Text(_String): class Html(_String): type = 'html' column_type = ('text', 'text') - _slots = { - 'sanitize': True, # whether value must be sanitized - 'sanitize_tags': True, # whether to sanitize tags (only a white list of attributes is accepted) - 'sanitize_attributes': True, # whether to sanitize attributes (only a white list of attributes is accepted) - 'sanitize_style': False, # whether to sanitize style attributes - 'sanitize_form': True, # whether to sanitize forms - 'strip_style': False, # whether to strip style attributes (removed and therefore not sanitized) - 'strip_classes': False, # whether to strip classes attributes - } + + sanitize = True # whether value must be sanitized + sanitize_tags = True # whether to sanitize tags (only a white list of attributes is accepted) + sanitize_attributes = True # whether to sanitize attributes (only a white list of attributes is accepted) + sanitize_style = False # whether to sanitize style attributes + sanitize_form = True # whether to sanitize forms + strip_style = False # whether to strip style attributes (removed and therefore not sanitized) + strip_classes = False # whether to strip classes attributes def _get_attrs(self, model, name): # called by _setup_attrs(), working together with _String._setup_attrs() @@ -1893,11 +1835,10 @@ _BINARY = memoryview class Binary(Field): type = 'binary' - _slots = { - 'prefetch': False, # not prefetched by default - 'depends_context': ('bin_size',), # depends on context (content or size) - 'attachment': True, # whether value is stored in attachment - } + + prefetch = False # not prefetched by default + depends_context = ('bin_size',) # depends on context (content or size) + attachment = True # whether value is stored in attachment @property def column_type(self): @@ -2076,11 +2017,9 @@ class Binary(Field): class Image(Binary): - _slots = { - 'max_width': 0, - 'max_height': 0, - 'verify_resolution': True, - } + max_width = 0 + max_height = 0 + verify_resolution = True def create(self, record_values): new_record_values = [] @@ -2156,11 +2095,10 @@ class Selection(Field): """ type = 'selection' column_type = ('varchar', pg_varchar()) - _slots = { - 'selection': None, # [(value, string), ...], function or method name - 'validate': True, # whether validating upon write - 'ondelete': None, # {value: policy} (what to do when value is deleted) - } + + selection = None # [(value, string), ...], function or method name + validate = True # whether validating upon write + ondelete = None # {value: policy} (what to do when value is deleted) def __init__(self, selection=Default, string=Default, **kwargs): super(Selection, self).__init__(selection=selection, string=string, **kwargs) @@ -2365,11 +2303,9 @@ class Reference(Selection): class _Relational(Field): """ Abstract class for relational fields. """ relational = True - _slots = { - 'domain': [], # domain for searching values - 'context': {}, # context for searching values - 'check_company': False, - } + domain = [] # domain for searching values + context = {} # context for searching values + check_company = False def __get__(self, records, owner): # base case: do the regular access @@ -2453,11 +2389,10 @@ class Many2one(_Relational): """ type = 'many2one' column_type = ('int4', 'int4') - _slots = { - 'ondelete': None, # what to do when value is deleted - 'auto_join': False, # whether joins are generated upon search - 'delegate': False, # whether self implements delegation - } + + ondelete = None # what to do when value is deleted + auto_join = False # whether joins are generated upon search + delegate = False # whether self implements delegation def __init__(self, comodel_name=Default, string=Default, **kwargs): super(Many2one, self).__init__(comodel_name=comodel_name, string=string, **kwargs) @@ -2668,9 +2603,8 @@ class Many2one(_Relational): class Many2oneReference(Integer): type = 'many2one_reference' - _slots = { - 'model_field': None, - } + + model_field = None _related_model_field = property(attrgetter('model_field')) @@ -2969,12 +2903,11 @@ class One2many(_RelationalMulti): the case of related fields or field extensions. """ type = 'one2many' - _slots = { - 'inverse_name': None, # name of the inverse field - 'auto_join': False, # whether joins are generated upon search - 'limit': None, # optional limit to use upon read - 'copy': False, # o2m are not copied by default - } + + inverse_name = None # name of the inverse field + auto_join = False # whether joins are generated upon search + limit = None # optional limit to use upon read + copy = False # o2m are not copied by default def __init__(self, comodel_name=Default, inverse_name=Default, string=Default, **kwargs): super(One2many, self).__init__( @@ -3249,15 +3182,14 @@ class Many2many(_RelationalMulti): :param int limit: optional limit to use upon read """ type = 'many2many' - _slots = { - '_explicit': True, # whether schema is explicitly given - 'relation': None, # name of table - 'column1': None, # column of table referring to model - 'column2': None, # column of table referring to comodel - 'auto_join': False, # whether joins are generated upon search - 'limit': None, # optional limit to use upon read - 'ondelete': None, # optional ondelete for the column2 fkey - } + + _explicit = True # whether schema is explicitly given + relation = None # name of table + column1 = None # column of table referring to model + column2 = None # column of table referring to comodel + auto_join = False # whether joins are generated upon search + limit = None # optional limit to use upon read + ondelete = None # optional ondelete for the column2 fkey def __init__(self, comodel_name=Default, relation=Default, column1=Default, column2=Default, string=Default, **kwargs): @@ -3656,12 +3588,11 @@ class Id(Field): """ Special case for field 'id'. """ type = 'integer' column_type = ('int4', 'int4') - _slots = { - 'string': 'ID', - 'store': True, - 'readonly': True, - 'prefetch': False, - } + + string = 'ID' + store = True + readonly = True + prefetch = False def update_db(self, model, columns): pass # this column is created with the table |
