16
16
from odoo .exceptions import AccessError , MissingError
17
17
from odoo .tools import Query , SQL , sql
18
18
from odoo .tools .constants import PREFETCH_MAX
19
- from odoo .tools .misc import SENTINEL , OrderedSet , Sentinel , unique
19
+ from odoo .tools .misc import SENTINEL , OrderedSet , ReadonlyDict , Sentinel , unique
20
20
21
21
from .domains import NEGATIVE_CONDITION_OPERATORS , Domain
22
22
from .utils import COLLECTION_TYPES , SQL_OPERATORS , SUPERUSER_ID , expand_ids
@@ -253,13 +253,13 @@ def _read_group_many2one_field(self, records, domain):
253
253
# Company-dependent fields are stored as jsonb (see column_type).
254
254
_column_type : tuple [str , str ] | None = None
255
255
256
- args : dict [str , typing .Any ] | None = None # the parameters given to __init__()
256
+ _args__ : dict [str , typing .Any ] | None = None # the parameters given to __init__()
257
257
_module : str | None = None # the field's module name
258
258
_modules : tuple [str , ...] = () # modules that define this field
259
259
_setup_done = True # whether the field is completely set up
260
260
_sequence : int # absolute ordering of the field
261
- _base_fields : tuple [Self , ...] = () # the fields defining self, in override order
262
- _extra_keys : tuple [str , ...] = () # unknown attributes set on the field
261
+ _base_fields__ : tuple [Self , ...] = () # the fields defining self, in override order
262
+ _extra_keys__ : tuple [str , ...] = () # unknown attributes set on the field
263
263
_direct : bool = False # whether self may be used directly (shared)
264
264
_toplevel : bool = False # whether self is on the model's registry class
265
265
@@ -304,12 +304,12 @@ def _read_group_many2one_field(self, records, domain):
304
304
exportable : bool = True
305
305
306
306
# mapping from type name to field type
307
- by_type : dict [str , Field ] = {}
307
+ _by_type__ : dict [str , Field ] = {}
308
308
309
309
def __init__ (self , string : str | Sentinel = SENTINEL , ** kwargs ):
310
310
kwargs ['string' ] = string
311
311
self ._sequence = next (_global_seq )
312
- self .args = {key : val for key , val in kwargs .items () if val is not SENTINEL }
312
+ self ._args__ = ReadonlyDict ( {key : val for key , val in kwargs .items () if val is not SENTINEL })
313
313
314
314
def __str__ (self ):
315
315
if not self .name :
@@ -327,7 +327,7 @@ def __init_subclass__(cls):
327
327
return
328
328
329
329
if cls .type :
330
- cls .by_type .setdefault (cls .type , cls )
330
+ cls ._by_type__ .setdefault (cls .type , cls )
331
331
332
332
# compute class attributes to avoid calling dir() on fields
333
333
cls .related_attrs = []
@@ -337,6 +337,8 @@ def __init_subclass__(cls):
337
337
cls .related_attrs .append ((attr [9 :], attr ))
338
338
elif attr .startswith ('_description_' ):
339
339
cls .description_attrs .append ((attr [13 :], attr ))
340
+ cls .related_attrs = tuple (cls .related_attrs )
341
+ cls .description_attrs = tuple (cls .description_attrs )
340
342
341
343
############################################################################
342
344
#
@@ -345,33 +347,33 @@ def __init_subclass__(cls):
345
347
# The base field setup is done by field.__set_name__(), which determines the
346
348
# field's name, model name, module and its parameters.
347
349
#
348
- # The dictionary field.args gives the parameters passed to the field's
350
+ # The dictionary field._args__ gives the parameters passed to the field's
349
351
# constructor. Most parameters have an attribute of the same name on the
350
352
# field. The parameters as attributes are assigned by the field setup.
351
353
#
352
354
# When several definition classes of the same model redefine a given field,
353
355
# the field occurrences are "merged" into one new field instantiated at
354
356
# runtime on the registry class of the model. The occurrences of the field
355
- # are given to the new field as the parameter '_base_fields '; it is a list
357
+ # are given to the new field as the parameter '_base_fields__ '; it is a list
356
358
# of fields in override order (or reverse MRO).
357
359
#
358
- # In order to save memory, a field should avoid having field.args and/or
360
+ # In order to save memory, a field should avoid having field._args__ and/or
359
361
# many attributes when possible. We call "direct" a field that can be set
360
362
# up directly from its definition class. Direct fields are non-related
361
363
# fields defined on models, and can be shared across registries. We call
362
364
# "toplevel" a field that is put on the model's registry class, and is
363
365
# therefore specific to the registry.
364
366
#
365
367
# Toplevel field are set up once, and are no longer set up from scratch
366
- # after that. Those fields can save memory by discarding field.args and
367
- # field._base_fields once set up, because those are no longer necessary.
368
+ # after that. Those fields can save memory by discarding field._args__ and
369
+ # field._base_fields__ once set up, because those are no longer necessary.
368
370
#
369
371
# Non-toplevel non-direct fields are the fields on definition classes that
370
372
# may not be shared. In other words, those fields are never used directly,
371
373
# and are always recreated as toplevel fields. On those fields, the base
372
- # setup is useless, because only field.args is used for setting up other
374
+ # setup is useless, because only field._args__ is used for setting up other
373
375
# fields. We therefore skip the base setup for those fields. The only
374
- # attributes of those fields are: '_sequence', 'args ', 'model_name', 'name'
376
+ # attributes of those fields are: '_sequence', '_args__ ', 'model_name', 'name'
375
377
# and '_module', which makes their __dict__'s size minimal.
376
378
377
379
def __set_name__ (self , owner : type [BaseModel ], name : str ) -> None :
@@ -391,14 +393,14 @@ def __set_name__(self, owner: type[BaseModel], name: str) -> None:
391
393
self ._module = owner ._module
392
394
owner ._field_definitions .append (self )
393
395
394
- if not self .args .get ('related' ):
396
+ if not self ._args__ .get ('related' ):
395
397
self ._direct = True
396
398
if self ._direct or self ._toplevel :
397
399
self ._setup_attrs__ (owner , name )
398
400
if self ._toplevel :
399
- # free memory, self.args and self._base_fields are no longer useful
400
- self .__dict__ .pop ('args ' , None )
401
- self .__dict__ .pop ('_base_fields ' , None )
401
+ # free memory, self._args__ and self._base_fields__ are no longer useful
402
+ self .__dict__ .pop ('_args__ ' , None )
403
+ self .__dict__ .pop ('_base_fields__ ' , None )
402
404
403
405
#
404
406
# Setup field parameter attributes
@@ -409,21 +411,20 @@ def _get_attrs(self, model_class: type[BaseModel], name: str) -> dict[str, typin
409
411
# determine all inherited field attributes
410
412
attrs = {}
411
413
modules : list [str ] = []
412
- for field in self .args .get ('_base_fields ' , ()):
414
+ for field in self ._args__ .get ('_base_fields__ ' , ()):
413
415
if not isinstance (self , type (field )):
414
416
# 'self' overrides 'field' and their types are not compatible;
415
417
# so we ignore all the parameters collected so far
416
418
attrs .clear ()
417
419
modules .clear ()
418
420
continue
419
- attrs .update (field .args )
421
+ attrs .update (field ._args__ )
420
422
if field ._module :
421
423
modules .append (field ._module )
422
- attrs .update (self .args )
424
+ attrs .update (self ._args__ )
423
425
if self ._module :
424
426
modules .append (self ._module )
425
427
426
- attrs ['args' ] = self .args
427
428
attrs ['model_name' ] = model_class ._name
428
429
attrs ['name' ] = name
429
430
attrs ['_module' ] = modules [- 1 ] if modules else None
@@ -486,9 +487,9 @@ def _setup_attrs__(self, model_class: type[BaseModel], name: str) -> None:
486
487
attrs = self ._get_attrs (model_class , name )
487
488
488
489
# determine parameters that must be validated
489
- extra_keys = [ key for key in attrs if not hasattr (self , key )]
490
+ extra_keys = tuple ( key for key in attrs if not hasattr (self , key ))
490
491
if extra_keys :
491
- attrs ['_extra_keys ' ] = extra_keys
492
+ attrs ['_extra_keys__ ' ] = extra_keys
492
493
493
494
self .__dict__ .update (attrs )
494
495
@@ -520,7 +521,7 @@ def setup(self, model: BaseModel) -> None:
520
521
""" Perform the complete setup of a field. """
521
522
if not self ._setup_done :
522
523
# validate field params
523
- for key in self ._extra_keys :
524
+ for key in self ._extra_keys__ :
524
525
if not model ._valid_field_parameter (self , key ):
525
526
_logger .warning (
526
527
"Field %s: unknown parameter %r, if this is an actual"
@@ -635,7 +636,7 @@ def setup_related(self, model: BaseModel) -> None:
635
636
if attr not in self .__dict__ and prop .startswith ('_related_' ):
636
637
setattr (self , attr , getattr (field , prop ))
637
638
638
- for attr in field ._extra_keys :
639
+ for attr in field ._extra_keys__ :
639
640
if not hasattr (self , attr ) and model ._valid_field_parameter (self , attr ):
640
641
setattr (self , attr , getattr (field , attr ))
641
642
0 commit comments