diff --git a/gears/connections.py b/gears/connections.py index c73f812..31624d7 100644 --- a/gears/connections.py +++ b/gears/connections.py @@ -1,4 +1,4 @@ -from typing import Any, Hashable, TypeVar, Callable +from typing import Any, Hashable, Optional, TypeVar, Callable from gears.effect import effect_of from gears.gear import Gear @@ -8,6 +8,8 @@ def connect[S: Hashable, T: Hashable]( source: Gear[S], to_target_val: Callable[[S], T], to_source: Callable[[S, T], S], + # i_know_what_im_doing: Optional[bool] = None, + i_know_what_im_doing: Optional[bool] = True, ) -> Gear[T]: target = Gear(to_target_val(source())) @@ -20,7 +22,10 @@ def connect[S: Hashable, T: Hashable]( def set_target_val(source_val: S): nonlocal entry_guard if entry_guard: - return + if i_know_what_im_doing: + return + else: + raise ValueError("You have a bad bidirectional mapping") entry_guard = True try: target.set(to_target_val(source_val)) @@ -31,7 +36,10 @@ def connect[S: Hashable, T: Hashable]( def set_source_val(target_val: T): nonlocal entry_guard if entry_guard: - return + if i_know_what_im_doing: + return + else: + raise ValueError("You have a bad bidirectional mapping") entry_guard = True try: source.set(to_source(source(), target_val)) diff --git a/gears/gear.py b/gears/gear.py index 4aa5438..6212949 100644 --- a/gears/gear.py +++ b/gears/gear.py @@ -5,6 +5,7 @@ class Gear[T: Hashable]: def __init__(self, value: T): self._value = value self.effects = [] + self.connection_effects = [] def get(self) -> T: return self._value @@ -13,8 +14,11 @@ class Gear[T: Hashable]: return self.get() def set(self, value: T): + print("setty: ", value) if value == self._value: return self._value = value + for effect in self.connection_effects: + effect.on_change(value) for effect in self.effects: effect.on_change(value) diff --git a/tests/test_signals.py b/tests/test_signals.py index 5b7af55..2116d1c 100644 --- a/tests/test_signals.py +++ b/tests/test_signals.py @@ -1,9 +1,11 @@ from dataclasses import dataclass from typing import NamedTuple from unittest.mock import MagicMock + +import pytest from gears import Gear from gears.connections import connect -from gears.effect import effect_of, effect_of_2 +from gears.effect import Effect, effect_of, effect_of_2 def test_get_set(): @@ -28,6 +30,9 @@ def test_basic_effect(): assert last_arg == "E" + g.set("hello") + assert last_arg == "hello" + def test_effect_of_2(): g1 = Gear("E") @@ -54,15 +59,87 @@ def test_connect(): assert my_str() == "321" +# def test_dualis(): +# val_both: Gear[tuple[int, int]] = Gear((5, 6)) +# val0 = connect(val_both, lambda both: both[0], lambda both, val0: (val0, both[1])) +# val1 = connect(val_both, lambda both: both[1], lambda both, val1: (both[0], val1)) + + +def test_watts(): + volts = Gear(3.0) + amps = Gear(5.0) + + @effect_of_2(volts, amps) + def on_watts_changed(volts: float, amps: float): + watts = volts * amps + if watts > 100: + raise ValueError("OMG ITS BURNING") + + volts.set(5.0) + with pytest.raises(ValueError): + volts.set(50.0) + + +def test_led(): + battery_voltage = Gear(3.0) + switch: Gear[bool] = Gear(True) # true means circuit closed + # ohms_circuit = Gear(1.0) + ohms_circuit = connect( + switch, lambda x: 1.0 if x else 100000000.0, lambda _, y: y < 100 + ) + + log: list[str] = [] + + @effect_of_2(battery_voltage, ohms_circuit) + def on_led_inputs_changed(volts: float, ohms: float): + amps = volts / ohms + if amps > 1: + print("LED ACTIVE!") + log.append("LED ACTIVE!") + else: + print("LED INACTIVE!") + log.append("LED INACTIVE!") + + # Effect(on_led_inputs_changed, battery_voltage, led_ohms) + + assert log == ["LED ACTIVE!"] + switch.set(False) + assert log == ["LED ACTIVE!", "LED INACTIVE!"] + + def test_connect_multiplication(): mul1 = Gear(10.0) mul2 = connect(mul1, lambda x: x * 2.0, lambda _, y: y / 2.0) + mul4 = connect(mul2, lambda x: x * 2.0, lambda _, y: y / 2.0) mul3 = connect(mul1, lambda x: x * 3.0, lambda _, y: y / 3.0) assert mul2() == 20 assert mul3() == 30 mul2.set(30) assert mul1() == 15 assert mul3() == 45 + assert mul4() == 60 + + # mul1 + # |> mul2 + # |> mul4 + # |> mul3 + # + + +class Person: + name: str + + +class HouseHold: + people: list[Person] + + +class NameEntry: + def __init__(self, name: Gear[str]): + # self.name = name + textBox = TextBox(...) + effect_of(name)(textBox.setValue) + textBox.onChange = name.set def test_connect_property():