Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/spellbind/bool_values.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
from __future__ import annotations

from abc import ABC
from typing import TypeVar, Generic, Callable

from spellbind.values import Value, DerivedValue, Constant

_S = TypeVar('_S')


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


class MappedBoolValue(Generic[_S], DerivedValue[_S, bool], BoolValue):
def __init__(self, value: Value[_S], transform: Callable[[_S], bool]) -> None:
self._transform = transform
super().__init__(value)

def transform(self, value: _S) -> bool:
return self._transform(value)


class NotBoolValue(DerivedValue[bool, bool], BoolValue):
def __init__(self, value: Value[bool]):
super().__init__(value)
Expand Down
9 changes: 9 additions & 0 deletions src/spellbind/float_values.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,15 @@ def __pos__(self) -> Self:
return self


class MappedFloatValue(Generic[_S], DerivedValue[_S, float], FloatValue):
def __init__(self, value: Value[_S], transform: Callable[[_S], float]) -> None:
self._transform = transform
super().__init__(value)

def transform(self, value: _S) -> float:
return self._transform(value)


class FloatConstant(FloatValue, Constant[float]):
pass

Expand Down
16 changes: 14 additions & 2 deletions src/spellbind/int_values.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from __future__ import annotations
from typing_extensions import Self
from typing_extensions import Self, TypeVar

import math
import operator
from abc import ABC
from typing import overload
from typing import overload, Generic, Callable

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


_S = TypeVar('_S')


class IntValue(Value[int], ABC):
@overload
def __add__(self, other: IntLike) -> IntValue: ...
Expand Down Expand Up @@ -128,6 +131,15 @@ def __pos__(self) -> Self:
return self


class MappedIntValue(Generic[_S], DerivedValue[_S, int], IntValue):
def __init__(self, value: Value[_S], transform: Callable[[_S], int]) -> None:
self._transform = transform
super().__init__(value)

def transform(self, value: _S) -> int:
return self._transform(value)


class IntConstant(IntValue, Constant[int]):
pass

Expand Down
13 changes: 12 additions & 1 deletion src/spellbind/str_values.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
from __future__ import annotations

from abc import ABC
from typing import Any
from typing import Any, Generic, Callable, TypeVar

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

StringLike = str | Value[str]

_S = TypeVar('_S')


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


class MappedStrValue(Generic[_S], DerivedValue[_S, str], StrValue):
def __init__(self, value: Value[_S], transform: Callable[[_S], str]) -> None:
self._transform = transform
super().__init__(value)

def transform(self, value: _S) -> str:
return self._transform(value)


class StrConstant(StrValue, Constant[str]):
pass

Expand Down
31 changes: 31 additions & 0 deletions src/spellbind/values.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

if TYPE_CHECKING:
from spellbind.str_values import StrValue
from spellbind.int_values import IntValue
from spellbind.bool_values import BoolValue


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

def map(self, transformer: Callable[[_S], _T]) -> Value[_T]:
return MappedValue(self, transformer)

def map_to_int(self, transformer: Callable[[_S], int]) -> IntValue:
from spellbind.int_values import MappedIntValue
return MappedIntValue(self, transformer)

def map_to_float(self, transformer: Callable[[_S], float]) -> Value[float]:
from spellbind.float_values import MappedFloatValue
return MappedFloatValue(self, transformer)

def map_to_str(self, transformer: Callable[[_S], str]) -> StrValue:
from spellbind.str_values import MappedStrValue
return MappedStrValue(self, transformer)

def map_to_bool(self, transformer: Callable[[_S], bool]) -> BoolValue:
from spellbind.bool_values import MappedBoolValue
return MappedBoolValue(self, transformer)


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


class MappedValue(DerivedValue[_S, _T], Generic[_S, _T]):
def __init__(self, of: Value[_S], transformer: Callable[[_S], _T]):
super().__init__(of)
self._transformer = transformer
self._of = of

def transform(self, value: _S) -> _T:
return self._transformer(value)


def _create_value_getter(value: Value[_S] | _S) -> Callable[[], _S]:
if isinstance(value, Value):
return lambda: value.value
Expand Down
File renamed without changes.
60 changes: 60 additions & 0 deletions tests/test_simple_variable.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import gc

import pytest

from spellbind.bool_values import BoolValue
from spellbind.float_values import FloatValue
from spellbind.int_values import IntValue
from spellbind.str_values import StrValue
from spellbind.values import SimpleVariable, Constant
from conftest import NoParametersObserver, OneParameterObserver

Expand Down Expand Up @@ -408,3 +413,58 @@ def test_simple_bool_variable_str():
def test_simple_none_variable_str():
none_bool = SimpleVariable(None)
assert str(none_bool) == "None"


def test_map_float_to_int():
value = SimpleVariable(42.5)
mapped = value.map_to_int(int)

assert isinstance(mapped, IntValue)
assert mapped.value == 42

value.value = 100.9
assert mapped.value == 100


def test_map_int_to_float():
value = SimpleVariable(42)
mapped = value.map_to_float(float)

assert isinstance(mapped, FloatValue)
assert mapped.value == 42.0

value.value = 100
assert mapped.value == 100.0


def test_map_int_to_str():
value = SimpleVariable(42)
mapped = value.map_to_str(str)

assert isinstance(mapped, StrValue)
assert mapped.value == "42"

value.value = 100
assert mapped.value == "100"


def test_map_int_to_bool():
value = SimpleVariable(0)
mapped = value.map_to_bool(bool)

assert isinstance(mapped, BoolValue)
assert mapped.value is False

value.value = 1
assert mapped.value is True


def test_map_str_to_int():
value = SimpleVariable("hello")
mapped = value.map_to_int(len)

assert isinstance(mapped, IntValue)
assert mapped.value == 5

value.value = "world!"
assert mapped.value == 6