Skip to content
This repository was archived by the owner on Aug 7, 2023. It is now read-only.

Commit a8e55e3

Browse files
authored
Merge pull request #1 from TheArtur128/development for 1.0.1 version
Code for 1.0.1 version
2 parents 5be3202 + 4a08c97 commit a8e55e3

File tree

2 files changed

+126
-59
lines changed

2 files changed

+126
-59
lines changed

Diff for: sculpting/core.py

+111-57
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
1+
from abc import ABC, abstractmethod
12
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
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
57
from 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
810
from 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+
)

Diff for: tests.py

+15-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Any, Type
22

3-
from pyhandling import times, by, then, operation_by, execute_operation, to, ArgumentPack
3+
from pyhandling import times, by, then, operation_by, execute_operation, to, ArgumentPack, event_as, raise_
44
from pytest import mark, raises
55

66
from sculpting.annotations import attribute_getter, attribute_setter
@@ -134,6 +134,14 @@ def test_attribute_setter(
134134
"_",
135135
AttributeError
136136
),
137+
(
138+
(Sculpture |to| AttributeKeeper())(
139+
_default_attribute_resource_factory=TypeError |then>> (event_as |to| raise_)
140+
),
141+
setting_of_attr("non_existent_attribute"),
142+
"_",
143+
TypeError
144+
)
137145
]
138146
)
139147
def test_attribute_setter_error_raising(
@@ -161,4 +169,9 @@ def test_sculpture_attribute_mapping(
161169

162170
setattr(sculpture, virtual_attribute_name, new_attribute_value)
163171

164-
assert getattr(sculpture, virtual_attribute_name) == getattr(mapped, original_attribute_name)
172+
assert getattr(sculpture, virtual_attribute_name) == getattr(mapped, original_attribute_name)
173+
174+
175+
@mark.parametrize("obj", [(AttributeKeeper(value=42), 42, None)])
176+
def test_original_from(obj: object):
177+
assert material_of(Sculpture(obj)) is obj

0 commit comments

Comments
 (0)