1
+ from abc import ABC , abstractmethod
1
2
from dataclasses import dataclass
2
- from typing import Final , Tuple , Iterable , Any , Callable , TypeVar , Generic
3
+ from typing import Final , Tuple , Iterable , Any , Callable , TypeVar , Generic , Optional , Union
3
4
4
- from pyhandling import returnly , by , documenting_by , mergely , take , close , post_partial , event_as , raise_ , then
5
+ from pyannotating import AnnotationTemplate , input_annotation
6
+ from pyhandling import returnly , by , documenting_by , mergely , take , close , post_partial , event_as , raise_ , then , to , next_action_decorator_of
5
7
from pyhandling .annotations import dirty , reformer_of , handler
6
8
7
- from sculpting .annotations import attribute_getter_of , attribute_setter_of , attribute_getter , attribute_setter
9
+ from sculpting .annotations import attribute_getter_of , attribute_setter_of , attribute_getter
8
10
from sculpting .tools import setting_of_attr
9
11
10
12
11
13
__all__ = (
12
14
"Sculpture" ,
15
+ "material_of" ,
13
16
"AttributeMap" ,
14
17
"attribute_map_for" ,
15
18
"read_only_attribute_map_as" ,
@@ -133,82 +136,133 @@ def changing_attribute_map_for(attribute_name: str, changer: reformer_of[Any]) -
133
136
)
134
137
135
138
136
- OriginalT = TypeVar ("OriginalT" )
139
+ _attribute_resource_for = (AnnotationTemplate | to | Union )([
140
+ str ,
141
+ AnnotationTemplate (AttributeMap , [input_annotation ]),
142
+ AnnotationTemplate (attribute_getter_of , [input_annotation ])
143
+ ])
137
144
138
145
139
- @_method_proxies_to_attribute ("__original" , set (_MAGIC_METHODS_NAMES ) - {"__repr__" , "__str__" })
140
- class Sculpture (Generic [OriginalT ]):
146
+ def _attribute_map_from (
147
+ attribute_resource : _attribute_resource_for [AttributeOwnerT ]
148
+ ) -> AttributeMap [AttributeOwnerT ]:
149
+ """
150
+ Function to cast an unstructured attribute resource into a map of that
151
+ attribute.
141
152
"""
142
- Virtual attribute mapping class for a real object.
143
153
144
- Virtual attribute names are given keyword arguments as keys.
145
- Values can be either the name of a real attribute of the original object, an
146
- AttributeMap, or a function to get the value of this attribute from the
147
- original object (in which case the attribute cannot be changed).
154
+ if isinstance (attribute_resource , AttributeMap ):
155
+ return attribute_resource
156
+ elif callable (attribute_resource ):
157
+ return read_only_attribute_map_as (attribute_resource )
158
+ else :
159
+ return attribute_map_for (attribute_resource )
160
+
161
+
162
+ class _DynamicAttributeKepper (Generic [AttributeOwnerT ], ABC ):
163
+ """
164
+ Class for managing attributes by delegating these responsibilities to
165
+ functions.
148
166
"""
149
167
150
168
def __init__ (
151
169
self ,
152
- original : OriginalT ,
153
- ** virtual_attribute_resource_by_virtual_attribute_name : str | AttributeMap [OriginalT ] | attribute_getter_of [OriginalT ]
170
+ * ,
171
+ _default_attribute_resource_factory : Optional [Callable [[str ], _attribute_resource_for [AttributeOwnerT ]]] = None ,
172
+ ** attribute_resource_by_attribute_name : _attribute_resource_for [AttributeOwnerT ],
154
173
):
155
- self .__original = original
156
- self .__attribute_map_by_virtual_attribute_name = _dict_value_map (
157
- self .__convert_virtual_attribute_resource_to_attribute_map ,
158
- virtual_attribute_resource_by_virtual_attribute_name
174
+ self ._attribute_map_by_attribute_name = _dict_value_map (
175
+ _attribute_map_from ,
176
+ attribute_resource_by_attribute_name
159
177
)
160
178
161
- def __repr__ (self ) -> str :
162
- return f"Sculpture from { self .__original } "
179
+ self ._default_attribute_map_for = (
180
+ _default_attribute_resource_factory | then >> _attribute_map_from
181
+ if _default_attribute_resource_factory is not None
182
+ else "Attribute \" {}\" is not allowed in {{}}" .format | then >> mergely (
183
+ take (AttributeMap ),
184
+ (getattr | by | "format" ) | then >> next_action_decorator_of (AttributeError | then >> raise_ ),
185
+ close (lambda template , obj , _ : raise_ (AttributeError (template .format (obj .__repr__ ()))))
186
+ )
187
+ )
163
188
164
189
def __getattr__ (self , attribute_name : str ) -> Any :
165
- if attribute_name [: 1 ] == '_' :
166
- return object .__getattribute__ (self , attribute_name )
167
-
168
- self .__validate_availability_for ( attribute_name )
169
-
170
- return self .__attribute_map_by_virtual_attribute_name [ attribute_name ]. getter (
171
- self . __original
190
+ return (
191
+ object .__getattribute__ (self , attribute_name )
192
+ if attribute_name [: 1 ] == '_'
193
+ else self ._attribute_value_for (
194
+ attribute_name ,
195
+ self .__attribute_map_for ( attribute_name )
196
+ )
172
197
)
173
198
174
199
def __setattr__ (self , attribute_name : str , attribute_value : Any ) -> Any :
175
- if attribute_name [: 1 ] == '_' :
200
+ return (
176
201
super ().__setattr__ (attribute_name , attribute_value )
177
- return
202
+ if attribute_name [:1 ] == '_'
203
+ else self ._set_attribute_value_for (
204
+ attribute_name ,
205
+ attribute_value ,
206
+ self .__attribute_map_for (attribute_name )
207
+ )
208
+ )
209
+
210
+ @abstractmethod
211
+ def _attribute_value_for (self , attribute_name : str , attribute_map : AttributeMap [AttributeOwnerT ]) -> Any :
212
+ """Method for getting the value for an attribute by its map."""
178
213
179
- self .__validate_availability_for (attribute_name )
214
+ @abstractmethod
215
+ def _set_attribute_value_for (self , attribute_name : str , attribute_value : Any , attribute_map : AttributeMap [AttributeOwnerT ]) -> Any :
216
+ """Method for setting a value for an attribute by its map."""
180
217
181
- return self .__attribute_map_by_virtual_attribute_name [attribute_name ].setter (
182
- self .__original ,
183
- attribute_value
218
+ def __attribute_map_for (self , attribute_name : str ) -> AttributeMap [AttributeOwnerT ]:
219
+ return (
220
+ self ._attribute_map_by_attribute_name [attribute_name ]
221
+ if attribute_name in self ._attribute_map_by_attribute_name .keys ()
222
+ else self ._default_attribute_map_for (attribute_name )
184
223
)
185
224
186
- def __validate_availability_for (self , attribute_name : str ) -> None :
187
- """
188
- Method of validation and possible subsequent error about the absence
189
- of such a virtual attribute.
190
- """
191
225
192
- if attribute_name not in self .__attribute_map_by_virtual_attribute_name .keys ():
193
- raise AttributeError (
194
- f"Attribute \" { attribute_name } \" is not allowed in { self .__repr__ ()} "
195
- )
226
+ OriginalT = TypeVar ("OriginalT" )
196
227
197
- @staticmethod
198
- def __convert_virtual_attribute_resource_to_attribute_map (
199
- virtual_attribute_resource : str | AttributeMap [OriginalT ] | attribute_getter_of [OriginalT ]
200
- ) -> AttributeMap [OriginalT ]:
201
- """
202
- Function to cast an unstructured virtual attribute resource into a map
203
- of that virtual attribute.
204
228
205
- Implements casting according to the rules defined in the Sculpture
206
- documentation.
207
- """
229
+ @_method_proxies_to_attribute ("__original" , set (_MAGIC_METHODS_NAMES ) - {"__repr__" , "__str__" })
230
+ class Sculpture (_DynamicAttributeKepper , Generic [OriginalT ]):
231
+ """
232
+ Virtual attribute mapping class for a real object.
233
+
234
+ Virtual attribute names are given keyword arguments as keys.
235
+ Values can be either the name of a real attribute of the original object, an
236
+ AttributeMap, or a function to get the value of this attribute from the
237
+ original object (in which case the attribute cannot be changed).
238
+ """
239
+
240
+ def __init__ (
241
+ self ,
242
+ original : OriginalT ,
243
+ * ,
244
+ _default_attribute_resource_factory : Optional [Callable [[str ], _attribute_resource_for [AttributeOwnerT ]]] = None ,
245
+ ** attribute_resource_by_attribute_name : _attribute_resource_for [OriginalT ],
246
+ ):
247
+ super ().__init__ (
248
+ _default_attribute_resource_factory = _default_attribute_resource_factory ,
249
+ ** attribute_resource_by_attribute_name
250
+ )
208
251
209
- if isinstance (virtual_attribute_resource , AttributeMap ):
210
- return virtual_attribute_resource
211
- elif callable (virtual_attribute_resource ):
212
- return read_only_attribute_map_as (virtual_attribute_resource )
213
- else :
214
- return attribute_map_for (virtual_attribute_resource )
252
+ self .__original = original
253
+
254
+ def __repr__ (self ) -> str :
255
+ return f"Sculpture from { self .__original } "
256
+
257
+ def _attribute_value_for (self , attribute_name : str , attribute_map : AttributeMap [OriginalT ]) -> Any :
258
+ return attribute_map .getter (self .__original )
259
+
260
+ def _set_attribute_value_for (self , attribute_name : str , attribute_value : Any , attribute_map : AttributeMap [OriginalT ]) -> Any :
261
+ return attribute_map .setter (self .__original , attribute_value )
262
+
263
+
264
+ material_of : Callable [[Sculpture [OriginalT ]], OriginalT ] = documenting_by (
265
+ """Function to get the object on which the input sculpture is based."""
266
+ )(
267
+ getattr | by | "_Sculpture__original"
268
+ )
0 commit comments