1
+ from inspect import getmembers , isfunction
1
2
from typing import Any , Callable , Dict , List , Tuple , Type , TYPE_CHECKING , TypeVar , Union
2
3
3
4
from pydantic import BaseModel
4
5
from pydantic .fields import ModelField
5
6
from typing_extensions import Final
6
7
8
+ from easyconfig import AppConfigMixin
7
9
from easyconfig .__const__ import MISSING , MISSING_TYPE
8
10
from easyconfig .config_objs import ConfigObjSubscription , SubscriptionParent
9
11
from easyconfig .errors import DuplicateSubscriptionError , FunctionCallNotAllowedError
16
18
HINT_CONFIG_OBJ_TYPE = Type [HINT_CONFIG_OBJ ]
17
19
18
20
21
+ NO_COPY = [n for n , o in getmembers (AppConfigMixin ) if isfunction (o )]
22
+
23
+
19
24
class ConfigObj :
20
25
def __init__ (self , model : BaseModel ,
21
26
path : Tuple [str , ...] = ('__root__' , ),
@@ -24,8 +29,9 @@ def __init__(self, model: BaseModel,
24
29
self ._obj_parent : Final = parent
25
30
self ._obj_path : Final = path
26
31
27
- self ._obj_model_fields : Dict [str , ModelField ] = model .__fields__
28
32
self ._obj_model_class : Final = model .__class__
33
+ self ._obj_model_fields : Dict [str , ModelField ] = model .__fields__
34
+ self ._obj_model_private_attrs : List [str ] = list (model .__private_attributes__ .keys ())
29
35
30
36
self ._obj_keys : Tuple [str , ...] = tuple ()
31
37
self ._obj_values : Dict [str , Any ] = {}
@@ -40,8 +46,21 @@ def from_model(cls, model: BaseModel,
40
46
path : Tuple [str , ...] = ('__root__' , ),
41
47
parent : Union [MISSING_TYPE , HINT_CONFIG_OBJ ] = MISSING ):
42
48
43
- ret = cls (model , path , parent )
44
-
49
+ # Copy functions from the class definition to the child class
50
+ functions = {}
51
+ for name , member in getmembers (model .__class__ ):
52
+ if not name .startswith ('_' ) and name not in NO_COPY and isfunction (member ):
53
+ functions [name ] = member
54
+
55
+ # Create a new class that pulls down the user defined functions if there are any
56
+ # It's not possible to attach the functions to the existing class instance
57
+ if functions :
58
+ new_cls = type (f'{ model .__class__ .__name__ } { cls .__name__ } ' , (cls , ), functions )
59
+ ret = new_cls (model , path , parent )
60
+ else :
61
+ ret = cls (model , path , parent )
62
+
63
+ # Set the values or create corresponding subclasses
45
64
keys = []
46
65
for key in ret ._obj_model_fields .keys ():
47
66
value = getattr (model , key , MISSING )
@@ -62,13 +81,27 @@ def from_model(cls, model: BaseModel,
62
81
# set child and values
63
82
setattr (ret , key , attrib )
64
83
84
+ # copy private attributes - these are the same as values
85
+ for key in ret ._obj_model_private_attrs :
86
+ value = getattr (model , key , MISSING )
87
+ if value is MISSING :
88
+ continue
89
+
90
+ keys .append (key )
91
+
92
+ ret ._obj_values [key ] = value
93
+ setattr (ret , key , value )
94
+
65
95
ret ._obj_keys = tuple (keys )
66
96
return ret
67
97
68
98
def _set_values (self , obj : BaseModel ) -> bool :
69
99
if not isinstance (obj , BaseModel ):
70
100
raise ValueError (f'Instance of { BaseModel .__class__ .__name__ } expected, got { obj } ({ type (obj )} )!' )
71
101
102
+ # Update last model so we can delegate function calls
103
+ self ._last_model = obj
104
+
72
105
value_changed = False
73
106
74
107
# Values of child objects
@@ -88,7 +121,7 @@ def _set_values(self, obj: BaseModel) -> bool:
88
121
value = getattr (obj , key , MISSING )
89
122
if value is MISSING :
90
123
continue
91
- old_value = self ._obj_values [ key ]
124
+ old_value = self ._obj_values . get ( key , MISSING )
92
125
self ._obj_values [key ] = value
93
126
94
127
# Update only values, child objects change in place
@@ -107,9 +140,9 @@ def _set_values(self, obj: BaseModel) -> bool:
107
140
def __repr__ (self ):
108
141
return f'<{ self .__class__ .__name__ } { "." .join (self ._obj_path )} >'
109
142
110
- def __getattr__ (self , item ):
111
- # delegate call to model
112
- return getattr (self ._last_model , item )
143
+ # def __getattr__(self, item):
144
+ # # delegate call to model
145
+ # return getattr(self._last_model, item)
113
146
114
147
# ------------------------------------------------------------------------------------------------------------------
115
148
# Match class signature with the Mixin Classes
0 commit comments