Skip to content

Commit 45636dc

Browse files
authored
Add map and map_to_X methods to Value (#47)
1 parent c430496 commit 45636dc

File tree

7 files changed

+138
-3
lines changed

7 files changed

+138
-3
lines changed

src/spellbind/bool_values.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,27 @@
11
from __future__ import annotations
22

33
from abc import ABC
4+
from typing import TypeVar, Generic, Callable
45

56
from spellbind.values import Value, DerivedValue, Constant
67

8+
_S = TypeVar('_S')
9+
710

811
class BoolValue(Value[bool], ABC):
912
def logical_not(self) -> BoolValue:
1013
return NotBoolValue(self)
1114

1215

16+
class MappedBoolValue(Generic[_S], DerivedValue[_S, bool], BoolValue):
17+
def __init__(self, value: Value[_S], transform: Callable[[_S], bool]) -> None:
18+
self._transform = transform
19+
super().__init__(value)
20+
21+
def transform(self, value: _S) -> bool:
22+
return self._transform(value)
23+
24+
1325
class NotBoolValue(DerivedValue[bool, bool], BoolValue):
1426
def __init__(self, value: Value[bool]):
1527
super().__init__(value)

src/spellbind/float_values.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,15 @@ def __pos__(self) -> Self:
9999
return self
100100

101101

102+
class MappedFloatValue(Generic[_S], DerivedValue[_S, float], FloatValue):
103+
def __init__(self, value: Value[_S], transform: Callable[[_S], float]) -> None:
104+
self._transform = transform
105+
super().__init__(value)
106+
107+
def transform(self, value: _S) -> float:
108+
return self._transform(value)
109+
110+
102111
class FloatConstant(FloatValue, Constant[float]):
103112
pass
104113

src/spellbind/int_values.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
from __future__ import annotations
2-
from typing_extensions import Self
2+
from typing_extensions import Self, TypeVar
33

44
import math
55
import operator
66
from abc import ABC
7-
from typing import overload
7+
from typing import overload, Generic, Callable
88

99
from spellbind.float_values import FloatValue, MultiplyFloatValues, DivideValues, SubtractFloatValues, \
1010
AddFloatValues, CompareNumbersValues
@@ -15,6 +15,9 @@
1515
FloatLike = IntLike | float | FloatValue
1616

1717

18+
_S = TypeVar('_S')
19+
20+
1821
class IntValue(Value[int], ABC):
1922
@overload
2023
def __add__(self, other: IntLike) -> IntValue: ...
@@ -128,6 +131,15 @@ def __pos__(self) -> Self:
128131
return self
129132

130133

134+
class MappedIntValue(Generic[_S], DerivedValue[_S, int], IntValue):
135+
def __init__(self, value: Value[_S], transform: Callable[[_S], int]) -> None:
136+
self._transform = transform
137+
super().__init__(value)
138+
139+
def transform(self, value: _S) -> int:
140+
return self._transform(value)
141+
142+
131143
class IntConstant(IntValue, Constant[int]):
132144
pass
133145

src/spellbind/str_values.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
from __future__ import annotations
22

33
from abc import ABC
4-
from typing import Any
4+
from typing import Any, Generic, Callable, TypeVar
55

66
from spellbind.values import Value, DerivedValue, CombinedMixedValues, SimpleVariable, Constant
77

88
StringLike = str | Value[str]
99

10+
_S = TypeVar('_S')
11+
1012

1113
class StrValue(Value[str], ABC):
1214
def __add__(self, other: StringLike) -> StrValue:
@@ -16,6 +18,15 @@ def __radd__(self, other: StringLike) -> StrValue:
1618
return ConcatenateStrValues(other, self)
1719

1820

21+
class MappedStrValue(Generic[_S], DerivedValue[_S, str], StrValue):
22+
def __init__(self, value: Value[_S], transform: Callable[[_S], str]) -> None:
23+
self._transform = transform
24+
super().__init__(value)
25+
26+
def transform(self, value: _S) -> str:
27+
return self._transform(value)
28+
29+
1930
class StrConstant(StrValue, Constant[str]):
2031
pass
2132

src/spellbind/values.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
if TYPE_CHECKING:
1010
from spellbind.str_values import StrValue
11+
from spellbind.int_values import IntValue
12+
from spellbind.bool_values import BoolValue
1113

1214

1315
EMPTY_FROZEN_SET: frozenset = frozenset()
@@ -64,6 +66,25 @@ def __str__(self) -> str:
6466
def __repr__(self) -> str:
6567
return f"{self.__class__.__name__}({self.value!r})"
6668

69+
def map(self, transformer: Callable[[_S], _T]) -> Value[_T]:
70+
return MappedValue(self, transformer)
71+
72+
def map_to_int(self, transformer: Callable[[_S], int]) -> IntValue:
73+
from spellbind.int_values import MappedIntValue
74+
return MappedIntValue(self, transformer)
75+
76+
def map_to_float(self, transformer: Callable[[_S], float]) -> Value[float]:
77+
from spellbind.float_values import MappedFloatValue
78+
return MappedFloatValue(self, transformer)
79+
80+
def map_to_str(self, transformer: Callable[[_S], str]) -> StrValue:
81+
from spellbind.str_values import MappedStrValue
82+
return MappedStrValue(self, transformer)
83+
84+
def map_to_bool(self, transformer: Callable[[_S], bool]) -> BoolValue:
85+
from spellbind.bool_values import MappedBoolValue
86+
return MappedBoolValue(self, transformer)
87+
6788

6889
class Variable(Value[_S], Generic[_S], ABC):
6990
@property
@@ -219,6 +240,16 @@ def value(self) -> _T:
219240
return self._value
220241

221242

243+
class MappedValue(DerivedValue[_S, _T], Generic[_S, _T]):
244+
def __init__(self, of: Value[_S], transformer: Callable[[_S], _T]):
245+
super().__init__(of)
246+
self._transformer = transformer
247+
self._of = of
248+
249+
def transform(self, value: _S) -> _T:
250+
return self._transformer(value)
251+
252+
222253
def _create_value_getter(value: Value[_S] | _S) -> Callable[[], _S]:
223254
if isinstance(value, Value):
224255
return lambda: value.value

tests/test_simple_variable.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import gc
22

33
import pytest
4+
5+
from spellbind.bool_values import BoolValue
6+
from spellbind.float_values import FloatValue
7+
from spellbind.int_values import IntValue
8+
from spellbind.str_values import StrValue
49
from spellbind.values import SimpleVariable, Constant
510
from conftest import NoParametersObserver, OneParameterObserver
611

@@ -408,3 +413,58 @@ def test_simple_bool_variable_str():
408413
def test_simple_none_variable_str():
409414
none_bool = SimpleVariable(None)
410415
assert str(none_bool) == "None"
416+
417+
418+
def test_map_float_to_int():
419+
value = SimpleVariable(42.5)
420+
mapped = value.map_to_int(int)
421+
422+
assert isinstance(mapped, IntValue)
423+
assert mapped.value == 42
424+
425+
value.value = 100.9
426+
assert mapped.value == 100
427+
428+
429+
def test_map_int_to_float():
430+
value = SimpleVariable(42)
431+
mapped = value.map_to_float(float)
432+
433+
assert isinstance(mapped, FloatValue)
434+
assert mapped.value == 42.0
435+
436+
value.value = 100
437+
assert mapped.value == 100.0
438+
439+
440+
def test_map_int_to_str():
441+
value = SimpleVariable(42)
442+
mapped = value.map_to_str(str)
443+
444+
assert isinstance(mapped, StrValue)
445+
assert mapped.value == "42"
446+
447+
value.value = 100
448+
assert mapped.value == "100"
449+
450+
451+
def test_map_int_to_bool():
452+
value = SimpleVariable(0)
453+
mapped = value.map_to_bool(bool)
454+
455+
assert isinstance(mapped, BoolValue)
456+
assert mapped.value is False
457+
458+
value.value = 1
459+
assert mapped.value is True
460+
461+
462+
def test_map_str_to_int():
463+
value = SimpleVariable("hello")
464+
mapped = value.map_to_int(len)
465+
466+
assert isinstance(mapped, IntValue)
467+
assert mapped.value == 5
468+
469+
value.value = "world!"
470+
assert mapped.value == 6

0 commit comments

Comments
 (0)