1+ from abc import ABC , abstractmethod
12from 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
34
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
57from pyhandling .annotations import dirty , reformer_of , handler
68
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
810from sculpting .tools import setting_of_attr
911
1012
1113__all__ = (
1214 "Sculpture" ,
15+ "material_of" ,
1316 "AttributeMap" ,
1417 "attribute_map_for" ,
1518 "read_only_attribute_map_as" ,
@@ -133,82 +136,133 @@ def changing_attribute_map_for(attribute_name: str, changer: reformer_of[Any]) -
133136 )
134137
135138
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+ ])
137144
138145
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.
141152 """
142- Virtual attribute mapping class for a real object.
143153
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.
148166 """
149167
150168 def __init__ (
151169 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 ],
154173 ):
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
159177 )
160178
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+ )
163188
164189 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+ )
172197 )
173198
174199 def __setattr__ (self , attribute_name : str , attribute_value : Any ) -> Any :
175- if attribute_name [: 1 ] == '_' :
200+ return (
176201 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."""
178213
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."""
180217
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 )
184223 )
185224
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- """
191225
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" )
196227
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.
204228
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+ )
208251
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