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
150168def __init__ (
151169self ,
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
164189def __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
174199def __setattr__ (self ,attribute_name :str ,attribute_value :Any )-> Any :
175- if attribute_name [: 1 ] == '_' :
200+ return (
176201super ().__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+ )