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
579 changes: 579 additions & 0 deletions src/spellbind/actions.py

Large diffs are not rendered by default.

4 changes: 0 additions & 4 deletions src/spellbind/bool_values.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,6 @@ def of(cls, value: bool) -> BoolConstant:
def logical_not(self) -> BoolConstant:
return BoolConstant.of(not self.value)

@property
def constant_value_or_raise(self) -> bool:
return self.value


class BoolVariable(SimpleVariable[bool], BoolValue):
pass
Expand Down
29 changes: 29 additions & 0 deletions src/spellbind/deriveds.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Iterable


class Derived(ABC):
@property
@abstractmethod
def derived_from(self) -> frozenset[Derived]: ...

@property
def deep_derived_from(self) -> Iterable[Derived]:
found_derived = set()
derive_queue = [self]

while derive_queue:
current = derive_queue.pop(0)
for dependency in current.derived_from:
if dependency not in found_derived:
found_derived.add(dependency)
yield dependency
derive_queue.append(dependency)

def is_derived_from(self, derived: Derived) -> bool:
for dependency in self.deep_derived_from:
if derived is dependency:
return True
return False
10 changes: 7 additions & 3 deletions src/spellbind/emitters.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from abc import ABC, abstractmethod
from typing import TypeVar, Generic

from typing import TypeVar, Generic, Iterable

T = TypeVar("T")
U = TypeVar("U")
Expand All @@ -14,7 +13,7 @@ def __call__(self) -> None: ...

class ValueEmitter(Generic[T], ABC):
@abstractmethod
def __call__(self, value0: T) -> None: ...
def __call__(self, value: T) -> None: ...


class BiEmitter(Generic[T, U], ABC):
Expand All @@ -25,3 +24,8 @@ def __call__(self, value0: T, value1: U) -> None: ...
class TriEmitter(Generic[T, U, S], ABC):
@abstractmethod
def __call__(self, value0: T, value1: U, value2: S) -> None: ...


class ValuesEmitter(Generic[T], ABC):
@abstractmethod
def __call__(self, values: Iterable[T]) -> None: ...
80 changes: 29 additions & 51 deletions src/spellbind/event.py
Original file line number Diff line number Diff line change
@@ -1,82 +1,60 @@
from abc import ABC, abstractmethod
from typing import Callable, TypeVar, Generic
from typing import Callable, TypeVar, Generic, Iterable, Sequence

from spellbind.emitters import Emitter, TriEmitter, BiEmitter, ValueEmitter
from spellbind.functions import assert_parameter_max_count
from spellbind.emitters import Emitter, TriEmitter, BiEmitter, ValueEmitter, ValuesEmitter
from spellbind.observables import Observable, ValueObservable, BiObservable, TriObservable, Observer, \
ValueObserver, BiObserver, TriObserver, Subscription, WeakSubscription, StrongSubscription, \
RemoveSubscriptionError
ValueObserver, BiObserver, TriObserver, ValuesObserver, ValuesObservable, _BaseObservable, _BaseValuesObservable, \
_SingleBaseObservable

_S = TypeVar("_S")
_T = TypeVar("_T")
_U = TypeVar("_U")
_O = TypeVar('_O', bound=Callable)


class _BaseEvent(Generic[_O], ABC):
_subscriptions: list[Subscription[_O]]

def __init__(self):
self._subscriptions = []

@abstractmethod
def _get_parameter_count(self) -> int: ...

def observe(self, observer: _O, times: int | None = None) -> None:
assert_parameter_max_count(observer, self._get_parameter_count())
self._subscriptions.append(StrongSubscription(observer, times))

def weak_observe(self, observer: _O, times: int | None = None) -> None:
assert_parameter_max_count(observer, self._get_parameter_count())
self._subscriptions.append(WeakSubscription(observer, times))

def unobserve(self, observer: _O) -> None:
for i, sub in enumerate(self._subscriptions):
if sub.matches_observer(observer):
del self._subscriptions[i]
return
raise ValueError(f"Observer {observer} is not subscribed to this event.")

def is_observed(self, observer: _O) -> bool:
return any(sub.matches_observer(observer) for sub in self._subscriptions)

def _emit(self, *args) -> None:
i = 0
while i < len(self._subscriptions):
try:
self._subscriptions[i](*args)
i += 1
except RemoveSubscriptionError:
del self._subscriptions[i]


class Event(_BaseEvent[Observer], Observable, Emitter):
class Event(_BaseObservable[Observer], Observable, Emitter):
def _get_parameter_count(self) -> int:
return 0

def __call__(self) -> None:
self._emit()
self._emit_nothing()


class ValueEvent(Generic[_S], _BaseEvent[Observer | ValueObserver[_S]], ValueObservable[_S], ValueEmitter[_S]):
class ValueEvent(Generic[_S], _SingleBaseObservable[Observer | ValueObserver[_S]], ValueObservable[_S], ValueEmitter[_S]):
def _get_parameter_count(self) -> int:
return 1

def __call__(self, value: _S) -> None:
self._emit(value)
self._emit_single(value)

def emit_lazy(self, func: Callable[[], _S]) -> None:
self._emit_single_lazy(func)

class BiEvent(Generic[_S, _T], _BaseEvent[Observer | ValueObserver[_S] | BiObserver[_S, _T]], BiObservable[_S, _T], BiEmitter[_S, _T]):

class BiEvent(Generic[_S, _T], _BaseObservable[Observer | ValueObserver[_S] | BiObserver[_S, _T]], BiObservable[_S, _T], BiEmitter[_S, _T]):
def _get_parameter_count(self) -> int:
return 2

def __call__(self, value_0: _S, value_1: _T) -> None:
self._emit(value_0, value_1)
self._emit_n((value_0, value_1))


class TriEvent(Generic[_S, _T, _U], _BaseEvent[Observer | ValueObserver[_S] | BiObserver[_S, _T] | TriObserver[_S, _T, _U]], TriObservable[_S, _T, _U], TriEmitter[_S, _T, _U]):
class TriEvent(Generic[_S, _T, _U],
_BaseObservable[Observer | ValueObserver[_S] | BiObserver[_S, _T] | TriObserver[_S, _T, _U]],
TriObservable[_S, _T, _U],
TriEmitter[_S, _T, _U]):
def _get_parameter_count(self) -> int:
return 3

def __call__(self, value_0: _S, value_1: _T, value_2: _U) -> None:
self._emit(value_0, value_1, value_2)
self._emit_n((value_0, value_1, value_2))


class ValuesEvent(Generic[_S], _BaseValuesObservable[Observer | ValuesObserver[_S]], ValuesObservable[_S], ValuesEmitter[_S]):
def __call__(self, value: Iterable[_S]) -> None:
self._emit_single(value)

def emit_single(self, value: _S) -> None:
self._emit_single((value,))

def emit_lazy(self, func: Callable[[], Sequence[_S]]) -> None:
self._emit_single_lazy(func)
8 changes: 3 additions & 5 deletions src/spellbind/float_values.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,6 @@
_U = TypeVar("_U")


_COMMUTATIVE_OPERATORS = {
operator.add, sum, _multiply_all_floats, max, min
}


def _average_float(values: Sequence[float]) -> float:
return sum(values) / len(values)

Expand Down Expand Up @@ -367,3 +362,6 @@ def __neg__(self) -> FloatValue:
class CompareNumbersValues(TwoFloatsToOneValue[bool], BoolValue):
def __init__(self, left: FloatLike, right: FloatLike, op: Callable[[float, float], bool]):
super().__init__(op, left, right)


ZERO = FloatConstant.of(0.)
5 changes: 5 additions & 0 deletions src/spellbind/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ def _is_positional_parameter(param: Parameter) -> bool:
return param.kind in (Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD)


def has_var_args(function: Callable) -> bool:
parameters = inspect.signature(function).parameters
return any(param.kind == Parameter.VAR_POSITIONAL for param in parameters.values())


def count_positional_parameters(function: Callable) -> int:
parameters = inspect.signature(function).parameters
return sum(1 for parameter in parameters.values() if _is_positional_parameter(parameter))
Expand Down
77 changes: 77 additions & 0 deletions src/spellbind/int_collections.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
from __future__ import annotations

import operator
from abc import ABC, abstractmethod
from functools import cached_property
from typing import Iterable, Callable, Any

from typing_extensions import TypeIs

from spellbind.int_values import IntValue, IntConstant
from spellbind.observable_collections import ObservableCollection, ReducedValue, CombinedValue, ValueCollection
from spellbind.observable_sequences import ObservableList, _S, TypedValueList, ValueSequence, UnboxedValueSequence, \
ObservableSequence
from spellbind.values import Value


class ObservableIntCollection(ObservableCollection[int], ABC):
@property
def summed(self) -> IntValue:
return self.reduce_to_int(add_reducer=operator.add, remove_reducer=operator.sub, initial=0)

@property
def multiplied(self) -> IntValue:
return self.reduce_to_int(add_reducer=operator.mul, remove_reducer=operator.floordiv, initial=1)


class ObservableIntSequence(ObservableSequence[int], ObservableIntCollection, ABC):
pass


class ObservableIntList(ObservableList[int], ObservableIntSequence):
pass


class IntValueCollection(ValueCollection[int], ABC):
@property
def summed(self) -> IntValue:
return self.unboxed.reduce_to_int(add_reducer=operator.add, remove_reducer=operator.sub, initial=0)

@property
@abstractmethod
def unboxed(self) -> ObservableIntCollection: ...


class CombinedIntValue(CombinedValue[int], IntValue):
def __init__(self, collection: ObservableCollection[_S], combiner: Callable[[Iterable[_S]], int]):
super().__init__(collection=collection, combiner=combiner)


class ReducedIntValue(ReducedValue[int], IntValue):
def __init__(self,
collection: ObservableCollection[_S],
add_reducer: Callable[[int, _S], int],
remove_reducer: Callable[[int, _S], int],
initial: int):
super().__init__(collection=collection,
add_reducer=add_reducer,
remove_reducer=remove_reducer,
initial=initial)


class UnboxedIntValueSequence(UnboxedValueSequence[int], ObservableIntSequence):
def __init__(self, sequence: IntValueSequence):
super().__init__(sequence)


class IntValueSequence(ValueSequence[int], IntValueCollection, ABC):
@cached_property
def unboxed(self) -> ObservableIntSequence:
return UnboxedIntValueSequence(self)


class IntValueList(TypedValueList[int], IntValueSequence):
def __init__(self, values: Iterable[int | Value[int]] | None = None):
def is_int(value: Any) -> TypeIs[int]:
return isinstance(value, int)
super().__init__(values, checker=is_int, constant_factory=IntConstant.of)
3 changes: 3 additions & 0 deletions src/spellbind/int_values.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,3 +252,6 @@ def __neg__(self) -> IntValue:
if isinstance(of, IntValue):
return of
return super().__neg__()


ZERO = IntConstant.of(0)
Loading