From 8eaa180add6cfe4dccf80dd4f299907dff8877c8 Mon Sep 17 00:00:00 2001 From: FancyNeuron <58030657+FancyNeuron@users.noreply.github.com> Date: Sat, 19 Jul 2025 17:03:45 +0200 Subject: [PATCH 1/2] Ensure @override is added to every overriding method/property (#74) Add @override decorators and make mypy check for missing override decorators --- experimentation.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 experimentation.py diff --git a/experimentation.py b/experimentation.py new file mode 100644 index 0000000..68459f3 --- /dev/null +++ b/experimentation.py @@ -0,0 +1,15 @@ +from typing import override + + +class Base: + def some_method(self) -> str: + return "This is a method from the Base class." + + +class Sub(Base): + def some_method(self) -> str: + return "This is a method from the Sub class." + + @override + def some_methods(self) -> str: + return "This is a method from the Sub class." From c501915c6425442474c168b39e140c1bda48bf1f Mon Sep 17 00:00:00 2001 From: Georg Plaz Date: Tue, 22 Jul 2025 19:28:52 +0200 Subject: [PATCH 2/2] Make mypy run in strict mode, fix violating mypy errors --- .github/workflows/python-package.yml | 2 +- src/spellbind/actions.py | 88 ++++---- src/spellbind/bool_values.py | 32 ++- src/spellbind/event.py | 4 +- src/spellbind/float_values.py | 78 +++++-- src/spellbind/functions.py | 10 +- src/spellbind/int_collections.py | 4 +- src/spellbind/int_values.py | 8 +- src/spellbind/observable_collections.py | 15 +- src/spellbind/observable_sequences.py | 210 +++++++++--------- src/spellbind/observables.py | 100 +++++---- src/spellbind/str_collections.py | 2 +- src/spellbind/str_values.py | 2 +- src/spellbind/values.py | 29 +-- tests/test_collections/test_empty_sequence.py | 6 +- 15 files changed, 325 insertions(+), 265 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index f712126..1afd702 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -31,7 +31,7 @@ jobs: flake8 . --count --max-complexity=10 --max-line-length=180 --show-source --statistics - name: Type check with mypy run: | - mypy src + mypy src --strict - name: Test with pytest run: | pytest --cov --cov-fail-under=95 diff --git a/src/spellbind/actions.py b/src/spellbind/actions.py index 2259adf..9ba7113 100644 --- a/src/spellbind/actions.py +++ b/src/spellbind/actions.py @@ -2,7 +2,7 @@ import itertools from abc import ABC, abstractmethod -from typing import Generic, SupportsIndex, Iterable, TypeVar, Callable +from typing import Generic, SupportsIndex, Iterable, TypeVar, Callable, Any from typing_extensions import override @@ -20,7 +20,7 @@ def is_permutation_only(self) -> bool: ... def map(self, transformer: Callable[[_S_co], _T]) -> CollectionAction[_T]: ... @override - def __repr__(self): + def __repr__(self) -> str: return f"{self.__class__.__name__}()" @@ -98,12 +98,12 @@ def map(self, transformer: Callable[[_S_co], _T]) -> AddOneAction[_T]: return SimpleAddOneAction(transformer(self.value)) @override - def __repr__(self): + def __repr__(self) -> str: return f"{self.__class__.__name__}(value={self.value})" class SimpleAddOneAction(AddOneAction[_S_co], Generic[_S_co]): - def __init__(self, item: _S_co): + def __init__(self, item: _S_co) -> None: self._item = item @property @@ -123,12 +123,12 @@ def map(self, transformer: Callable[[_S_co], _T]) -> RemoveOneAction[_T]: return SimpleRemoveOneAction(transformer(self.value)) @override - def __repr__(self): + def __repr__(self) -> str: return f"{self.__class__.__name__}(value={self.value})" class SimpleRemoveOneAction(RemoveOneAction[_S_co], Generic[_S_co]): - def __init__(self, item: _S_co): + def __init__(self, item: _S_co) -> None: self._item = item @property @@ -167,13 +167,13 @@ def changes(self) -> Iterable[OneElementChangedAction[_S_co]]: return self._changes @override - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, ElementsChangedAction): return NotImplemented return self.changes == other.changes @override - def __repr__(self): + def __repr__(self) -> str: return f"{self.__class__.__name__}(changes={self.changes})" @@ -212,22 +212,22 @@ def old_item(self) -> _S_co: return self._old_item @override - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, OneElementChangedAction): return NotImplemented - return (self.new_item == other.new_item and - self.old_item == other.old_item) + # mypy --strict complains that equality between two "Any" does return Any, not bool + return bool(self.new_item == other.new_item and self.old_item == other.old_item) @override - def __repr__(self): + def __repr__(self) -> str: return f"{self.__class__.__name__}(new_item={self.new_item}, old_item={self.old_item})" -CLEAR_ACTION: ClearAction = ClearAction() +_CLEAR_ACTION: ClearAction[Any] = ClearAction() def clear_action() -> ClearAction[_S_co]: - return CLEAR_ACTION # type: ignore[return-value] + return _CLEAR_ACTION class SequenceAction(CollectionAction[_S_co], Generic[_S_co], ABC): @@ -272,7 +272,7 @@ def delta_actions(self) -> tuple[AtIndexDeltaAction[_S_co], ...]: def map(self, transformer: Callable[[_S_co], _T]) -> AtIndexDeltaAction[_T]: ... @override - def __repr__(self): + def __repr__(self) -> str: return f"{self.__class__.__name__}(index={self.index}, value={self.value})" @@ -283,7 +283,7 @@ def map(self, transformer: Callable[[_S_co], _T]) -> InsertAction[_T]: class SimpleInsertAction(InsertAction[_S_co], Generic[_S_co]): - def __init__(self, index: SupportsIndex, item: _S_co): + def __init__(self, index: SupportsIndex, item: _S_co) -> None: self._index = index self._item = item @@ -298,10 +298,11 @@ def value(self) -> _S_co: return self._item @override - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, InsertAction): return NotImplemented - return self.index == other.index and self.value == other.value + # mypy --strict complains that equality between two "Any" does return Any, not bool + return self.index == other.index and bool(self.value == other.value) class InsertAllAction(AtIndicesDeltasAction[_S_co], Generic[_S_co], ABC): @@ -319,10 +320,11 @@ def map(self, transformer: Callable[[_S_co], _T]) -> InsertAllAction[_T]: return SimpleInsertAllAction(tuple((index, transformer(item)) for index, item in self.index_with_items)) @override - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, InsertAllAction): return NotImplemented - return self.index_with_items == other.index_with_items + # mypy --strict complains that equality between two "Any" does return Any, not bool + return bool(self.index_with_items == other.index_with_items) class SimpleInsertAllAction(InsertAllAction[_S_co], Generic[_S_co]): @@ -341,14 +343,15 @@ def map(self, transformer: Callable[[_S_co], _T]) -> RemoveAtIndexAction[_T]: return SimpleRemoveAtIndexAction(self.index, transformer(self.value)) @override - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, RemoveAtIndexAction): return NotImplemented - return self.index == other.index and self.value == other.value + # mypy --strict complains that equality between two "Any" does return Any, not bool + return self.index == other.index and bool(self.value == other.value) class SimpleRemoveAtIndexAction(RemoveAtIndexAction[_S_co], RemoveOneAction[_S_co], Generic[_S_co]): - def __init__(self, index: SupportsIndex, item: _S_co): + def __init__(self, index: SupportsIndex, item: _S_co) -> None: self._index = index.__index__() self._item = item @@ -377,13 +380,13 @@ def map(self, transformer: Callable[[_S_co], _T]) -> RemoveAtIndicesAction[_T]: )) @override - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, RemoveAtIndicesAction): return NotImplemented - return self.removed_elements_with_index == other.removed_elements_with_index + return bool(self.removed_elements_with_index == other.removed_elements_with_index) @override - def __repr__(self): + def __repr__(self) -> str: return f"{self.__class__.__name__}({self.removed_elements_with_index})" @@ -439,16 +442,17 @@ def map(self, transformer: Callable[[_S_co], _T]) -> SetAtIndexAction[_T]: return SimpleSetAtIndexAction(self.index, old_item=transformer(self.old_item), new_item=transformer(self.new_item)) @override - def __repr__(self): + def __repr__(self) -> str: return f"{self.__class__.__name__}(index={self.index}, old_item={self.old_item}, new_item={self.new_item})" @override - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, SetAtIndexAction): return NotImplemented + # mypy --strict complains that equality between two "Any" does return Any, not bool return (self.index == other.index and - self.old_item == other.old_item and - self.new_item == other.new_item) + bool(self.old_item == other.old_item and + self.new_item == other.new_item)) class SimpleSetAtIndexAction(SetAtIndexAction[_S_co], Generic[_S_co]): @@ -498,15 +502,16 @@ def map(self, transformer: Callable[[_S_co], _T]) -> SliceSetAction[_T]: old_items=tuple(transformer(item) for item in self.old_items)) @override - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, SliceSetAction): return NotImplemented + # mypy --strict complains that equality between two "Any" does return Any, not bool return (self.indices == other.indices and - self.new_items == other.new_items and - self.old_items == other.old_items) + bool(self.new_items == other.new_items and + self.old_items == other.old_items)) @override - def __repr__(self): + def __repr__(self) -> str: return (f"{self.__class__.__name__}(indices={self.indices}, " f"new_items={self.new_items}, old_items={self.old_items})") @@ -571,13 +576,14 @@ def map(self, transformer: Callable[[_S_co], _T]) -> SetAtIndicesAction[_T]: in self.indices_with_new_and_old_items)) @override - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, SetAtIndicesAction): return NotImplemented - return self.indices_with_new_and_old_items == other.indices_with_new_and_old_items + # mypy --strict complains that equality between two "Any" does return Any, not bool + return bool(self.indices_with_new_and_old_items == other.indices_with_new_and_old_items) @override - def __repr__(self): + def __repr__(self) -> str: return f"{self.__class__.__name__}(indices_with_new_and_old_items={self.indices_with_new_and_old_items})" @@ -633,14 +639,14 @@ def map(self, transformer: Callable[[_S_co], _T]) -> ExtendAction[_T]: return SimpleExtendAction(self.old_sequence_length, tuple(transformer(item) for item in self.items)) @override - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, ExtendAction): return NotImplemented return (self.old_sequence_length == other.old_sequence_length and tuple(self.items) == tuple(other.items)) @override - def __repr__(self): + def __repr__(self) -> str: return f"{self.__class__.__name__}(old_sequence_length={self.old_sequence_length}, items={self.items})" @@ -660,8 +666,8 @@ def old_sequence_length(self) -> int: return self._old_sequence_length -REVERSE_SEQUENCE_ACTION: ReverseAction = ReverseAction() +REVERSE_SEQUENCE_ACTION: ReverseAction[Any] = ReverseAction() def reverse_action() -> ReverseAction[_S_co]: - return REVERSE_SEQUENCE_ACTION # type: ignore[return-value] + return REVERSE_SEQUENCE_ACTION diff --git a/src/spellbind/bool_values.py b/src/spellbind/bool_values.py index 18c2c19..56f8b54 100644 --- a/src/spellbind/bool_values.py +++ b/src/spellbind/bool_values.py @@ -60,8 +60,20 @@ def __xor__(self, other: BoolLike) -> BoolValue: def __rxor__(self, other: bool) -> BoolValue: return BoolValue.derive_from_two(operator.xor, other, self) - @overload - def select(self, if_true: IntValueLike, if_false: IntValueLike) -> IntValue: ... + def select_int(self, if_true: IntLike, if_false: IntLike) -> IntValue: + from spellbind.int_values import IntValue + return IntValue.derive_from_three(_select_function, self, if_true, if_false) + + def select_float(self, if_true: FloatLike, if_false: FloatLike) -> FloatValue: + from spellbind.float_values import FloatValue + return FloatValue.derive_from_three(_select_function, self, if_true, if_false) + + def select_bool(self, if_true: BoolLike, if_false: BoolLike) -> BoolValue: + return BoolValue.derive_from_three(_select_function, self, if_true, if_false) + + def select_str(self, if_true: StrLike, if_false: StrLike) -> StrValue: + from spellbind.str_values import StrValue + return StrValue.derive_from_three(_select_function, self, if_true, if_false) @overload def select(self, if_true: FloatValueLike, if_false: FloatValueLike) -> FloatValue: ... @@ -75,19 +87,21 @@ def select(self, if_true: BoolValue, if_false: BoolValue) -> BoolValue: ... @overload def select(self, if_true: Value[_S] | _S, if_false: Value[_S] | _S) -> Value[_S]: ... - def select(self, if_true, if_false): + def select(self, if_true: Value[_S] | _S, if_false: Value[_S] | _S) -> Value[_S]: + from spellbind.str_values import StrValue from spellbind.float_values import FloatValue from spellbind.int_values import IntValue - from spellbind.str_values import StrValue + # suppressing errors, because it seems mypy does not understand the connection between + # parameter type and return type as it could be inferred from the overloads if isinstance(if_true, (FloatValue, float)) and isinstance(if_false, (FloatValue, float)): - return FloatValue.derive_from_three(_select_function, self, if_true, if_false) + return self.select_float(if_true, if_false) # type: ignore[return-value] elif isinstance(if_true, (StrValue, str)) and isinstance(if_false, (StrValue, str)): - return StrValue.derive_from_three(_select_function, self, if_true, if_false) + return self.select_str(if_true, if_false) # type: ignore[return-value] elif isinstance(if_true, (BoolValue, bool)) and isinstance(if_false, (BoolValue, bool)): - return BoolValue.derive_from_three(_select_function, self, if_true, if_false) + return self.select_bool(if_true, if_false) # type: ignore[return-value] elif isinstance(if_true, (IntValue, int)) and isinstance(if_false, (IntValue, int)): - return IntValue.derive_from_three(_select_function, self, if_true, if_false) + return self.select_int(if_true, if_false) # type: ignore[return-value] else: return Value.derive_three_value(_select_function, self, if_true, if_false) @@ -127,7 +141,7 @@ class OneToBoolValue(OneToOneValue[_S, bool], BoolValue, Generic[_S]): class NotBoolValue(OneToOneValue[bool, bool], BoolValue): - def __init__(self, value: Value[bool]): + def __init__(self, value: Value[bool]) -> None: super().__init__(operator.not_, value) diff --git a/src/spellbind/event.py b/src/spellbind/event.py index bb6600a..c412fee 100644 --- a/src/spellbind/event.py +++ b/src/spellbind/event.py @@ -1,4 +1,4 @@ -from typing import Callable, TypeVar, Generic, Iterable, Sequence +from typing import Callable, TypeVar, Generic, Iterable, Sequence, Any from typing_extensions import override @@ -10,7 +10,7 @@ _S = TypeVar("_S") _T = TypeVar("_T") _U = TypeVar("_U") -_O = TypeVar('_O', bound=Callable) +_O = TypeVar('_O', bound=Callable[..., Any]) class Event(_BaseObservable[Observer], Observable, Emitter): diff --git a/src/spellbind/float_values.py b/src/spellbind/float_values.py index 69bc209..e00e7d2 100644 --- a/src/spellbind/float_values.py +++ b/src/spellbind/float_values.py @@ -11,7 +11,7 @@ from spellbind.bool_values import BoolValue from spellbind.functions import _clamp_float, _multiply_all_floats from spellbind.values import Value, SimpleVariable, OneToOneValue, DerivedValueBase, Constant, \ - NotConstantError, ThreeToOneValue + NotConstantError, ThreeToOneValue, _create_value_getter, get_constant_of_generic_like if TYPE_CHECKING: from spellbind.int_values import IntValue, IntLike # pragma: no cover @@ -89,7 +89,7 @@ def round(self, ndigits: IntLike | None = None) -> FloatValue | IntValue: round_to_int_fun: Callable[[float], int] = round return IntValue.derive_from_one(round_to_int_fun, self) round_fun: Callable[[float, int], float] = round - return FloatValue.derive_from_two(round_fun, self, ndigits) + return FloatValue.derive_from_flot_and_int(round_fun, self, ndigits) def __lt__(self, other: FloatLike) -> BoolValue: return CompareNumbersValues(self, other, operator.lt) @@ -112,7 +112,7 @@ def __pos__(self) -> Self: def clamp(self, min_value: FloatLike, max_value: FloatLike) -> FloatValue: return FloatValue.derive_from_three_floats(_clamp_float, self, min_value, max_value) - def decompose_float_operands(self, operator_: Callable[[Sequence[float]], _S]) -> Sequence[FloatLike]: + def decompose_float_operands(self, operator_: Callable[..., float]) -> Sequence[FloatLike]: return (self,) @classmethod @@ -125,25 +125,34 @@ def derive_from_one(cls, transformer: Callable[[float], float], of: FloatLike) - return FloatConstant.of(transformer(constant_value)) @classmethod - @overload - def derive_from_two(cls, operator_: Callable[[float, int], float], first: FloatLike, second: IntLike) -> FloatValue: ... - - @classmethod - @overload - def derive_from_two(cls, operator_: Callable[[float, float], float], first: FloatLike, second: FloatLike) -> FloatValue: ... + def derive_from_flot_and_int(cls, + operator_: Callable[[float, int], float], + first: FloatLike, + second: IntLike) -> FloatValue: + try: + constant_first = _get_constant_float(first) + constant_second = get_constant_of_generic_like(second) + except NotConstantError: + return FloatAndIntToFloatValue(operator_, first, second) + else: + return FloatConstant.of(operator_(constant_first, constant_second)) @classmethod - def derive_from_two(cls, operator_, first, second) -> FloatValue: + def derive_from_two(cls, + operator_: Callable[[float, float], float], + first: FloatLike, + second: FloatLike) -> FloatValue: try: constant_first = _get_constant_float(first) constant_second = _get_constant_float(second) except NotConstantError: - return TwoFloatsToFloatsValue(operator_, first, second) + return TwoFloatsToFloatValue(operator_, first, second) else: return FloatConstant.of(operator_(constant_first, constant_second)) @classmethod - def derive_from_three_floats(cls, transformer: Callable[[float, float, float], float], + def derive_from_three_floats(cls, + transformer: Callable[[float, float, float], float], first: FloatLike, second: FloatLike, third: FloatLike) -> FloatValue: try: constant_first = _get_constant_float(first) @@ -214,13 +223,13 @@ def of(cls, value: float) -> FloatConstant: return FloatConstant(value) @override - def __abs__(self): + def __abs__(self) -> FloatConstant: if self.value >= 0: return self return FloatConstant.of(-self.value) @override - def __neg__(self): + def __neg__(self) -> FloatConstant: return FloatConstant.of(-self.value) @@ -241,7 +250,7 @@ def _create_float_getter(value: float | Value[int] | Value[float]) -> Callable[[ class OneFloatToOneValue(DerivedValueBase[_S], Generic[_S]): - def __init__(self, transformer: Callable[[float], _S], of: FloatLike): + def __init__(self, transformer: Callable[[float], _S], of: FloatLike) -> None: self._of = of self._getter = _create_float_getter(of) self._transformer = transformer @@ -267,7 +276,7 @@ def _get_constant_float(value: FloatLike) -> float: return value -def _decompose_float_operands(operator_: Callable, value: FloatLike) -> Sequence[FloatLike]: +def _decompose_float_operands(operator_: Callable[..., float], value: FloatLike) -> Sequence[FloatLike]: if isinstance(value, Value): if isinstance(value, FloatValue): return value.decompose_float_operands(operator_) @@ -290,7 +299,7 @@ def _calculate_value(self) -> _S: class ManyFloatsToFloatValue(ManyFloatsToOneValue[float], FloatValue): @override - def decompose_float_operands(self, operator_: Callable) -> Sequence[FloatLike]: + def decompose_float_operands(self, operator_: Callable[..., float]) -> Sequence[FloatLike]: if self._transformer == operator_: return self._input_values return (self,) @@ -311,9 +320,32 @@ def _calculate_value(self) -> _S: return self._transformer(self._first_getter(), self._second_getter()) -class TwoFloatsToFloatsValue(TwoFloatsToOneValue[float], FloatValue): +class FloatAndIntToOneValue(DerivedValueBase[_S], Generic[_S]): + def __init__(self, transformer: Callable[[float, int], _S], + first: FloatLike, second: IntLike): + self._transformer = transformer + self._of_first = first + self._of_second = second + self._first_getter = _create_float_getter(first) + self._second_getter = _create_value_getter(second) + super().__init__(*[v for v in (first, second) if isinstance(v, Value)]) + + @override + def _calculate_value(self) -> _S: + return self._transformer(self._first_getter(), self._second_getter()) + + +class TwoFloatsToFloatValue(TwoFloatsToOneValue[float], FloatValue): + @override + def decompose_float_operands(self, operator_: Callable[..., float]) -> Sequence[FloatLike]: + if self._transformer == operator_: + return self._of_first, self._of_second + return (self,) + + +class FloatAndIntToFloatValue(FloatAndIntToOneValue[float], FloatValue, Generic[_S]): @override - def decompose_float_operands(self, operator_: Callable) -> Sequence[FloatLike]: + def decompose_float_operands(self, operator_: Callable[..., float]) -> Sequence[FloatLike]: if self._transformer == operator_: return self._of_first, self._of_second return (self,) @@ -338,7 +370,7 @@ def _calculate_value(self) -> _S: class ThreeFloatToFloatValue(ThreeFloatToOneValue[float], FloatValue): @override - def decompose_float_operands(self, operator_: Callable) -> Sequence[FloatLike]: + def decompose_float_operands(self, operator_: Callable[..., float]) -> Sequence[FloatLike]: if self._transformer == operator_: return self._of_first, self._of_second, self._of_third return (self,) @@ -353,7 +385,7 @@ def create(cls, transformer: Callable[[_S, _T, _U], float], class AbsFloatValue(OneFloatToOneValue[float], FloatValue): - def __init__(self, value: FloatLike): + def __init__(self, value: FloatLike) -> None: super().__init__(abs, value) @override @@ -362,7 +394,7 @@ def __abs__(self) -> Self: class NegateFloatValue(OneFloatToFloatValue, FloatValue): - def __init__(self, value: FloatLike): + def __init__(self, value: FloatLike) -> None: super().__init__(operator.neg, value) @override @@ -374,7 +406,7 @@ def __neg__(self) -> FloatValue: class CompareNumbersValues(TwoFloatsToOneValue[bool], BoolValue): - def __init__(self, left: FloatLike, right: FloatLike, op: Callable[[float, float], bool]): + def __init__(self, left: FloatLike, right: FloatLike, op: Callable[[float, float], bool]) -> None: super().__init__(op, left, right) diff --git a/src/spellbind/functions.py b/src/spellbind/functions.py index d2dbb51..5c148f8 100644 --- a/src/spellbind/functions.py +++ b/src/spellbind/functions.py @@ -1,18 +1,18 @@ import inspect from inspect import Parameter -from typing import Callable, Iterable +from typing import Callable, Iterable, Any 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: +def has_var_args(function: Callable[..., Any]) -> 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: +def count_positional_parameters(function: Callable[..., Any]) -> int: parameters = inspect.signature(function).parameters return sum(1 for parameter in parameters.values() if _is_positional_parameter(parameter)) @@ -21,12 +21,12 @@ def _is_required_positional_parameter(param: Parameter) -> bool: return param.default == param.empty and _is_positional_parameter(param) -def count_non_default_parameters(function: Callable) -> int: +def count_non_default_parameters(function: Callable[..., Any]) -> int: parameters = inspect.signature(function).parameters return sum(1 for param in parameters.values() if _is_required_positional_parameter(param)) -def assert_parameter_max_count(callable_: Callable, max_count: int) -> None: +def assert_parameter_max_count(callable_: Callable[..., Any], max_count: int) -> None: if count_non_default_parameters(callable_) > max_count: if hasattr(callable_, '__name__'): callable_name = callable_.__name__ diff --git a/src/spellbind/int_collections.py b/src/spellbind/int_collections.py index 2115603..9e9ff10 100644 --- a/src/spellbind/int_collections.py +++ b/src/spellbind/int_collections.py @@ -43,7 +43,7 @@ def unboxed(self) -> ObservableIntCollection: ... class CombinedIntValue(CombinedValue[int], IntValue): - def __init__(self, collection: ObservableCollection[_S], combiner: Callable[[Iterable[_S]], int]): + def __init__(self, collection: ObservableCollection[_S], combiner: Callable[[Iterable[_S]], int]) -> None: super().__init__(collection=collection, combiner=combiner) @@ -60,7 +60,7 @@ def __init__(self, class UnboxedIntValueSequence(UnboxedValueSequence[int], ObservableIntSequence): - def __init__(self, sequence: IntValueSequence): + def __init__(self, sequence: IntValueSequence) -> None: super().__init__(sequence) diff --git a/src/spellbind/int_values.py b/src/spellbind/int_values.py index 9c8997c..3402168 100644 --- a/src/spellbind/int_values.py +++ b/src/spellbind/int_values.py @@ -214,13 +214,13 @@ def of(cls, value: int) -> IntConstant: return IntConstant(value) @override - def __abs__(self): + def __abs__(self) -> IntConstant: if self.value >= 0: return self return IntConstant.of(-self.value) @override - def __neg__(self): + def __neg__(self) -> IntConstant: return IntConstant.of(-self.value) @@ -240,7 +240,7 @@ def create(transformer: Callable[[Iterable[int]], int], values: Iterable[IntLike class AbsIntValue(OneToOneValue[int, int], IntValue): - def __init__(self, value: Value[int]): + def __init__(self, value: Value[int]) -> None: super().__init__(abs, value) @override @@ -249,7 +249,7 @@ def __abs__(self) -> Self: class NegateIntValue(OneToOneValue[int, int], IntValue): - def __init__(self, value: Value[int]): + def __init__(self, value: Value[int]) -> None: super().__init__(operator.neg, value) @override diff --git a/src/spellbind/observable_collections.py b/src/spellbind/observable_collections.py index e7c8d13..9795403 100644 --- a/src/spellbind/observable_collections.py +++ b/src/spellbind/observable_collections.py @@ -7,6 +7,7 @@ from typing_extensions import override from spellbind.actions import CollectionAction, DeltaAction, DeltasAction, ClearAction +from spellbind.deriveds import Derived from spellbind.event import BiEvent from spellbind.int_values import IntValue from spellbind.observables import ValuesObservable, ValueObservable, BiObservable @@ -52,8 +53,6 @@ def reduce(self, add_reducer: Callable[[_T, _S_co], _T], remove_reducer: Callable[[_T, _S_co], _T], initial: _T) -> Value[_T]: - from spellbind.str_collections import ReducedValue - return ReducedValue(self, add_reducer=add_reducer, remove_reducer=remove_reducer, @@ -97,7 +96,7 @@ def __init__(self, self._collection.on_change.observe(self._on_action) self._on_change: BiEvent[_S, _S] = BiEvent[_S, _S]() - def _on_action(self, action: CollectionAction[_T]): + def _on_action(self, action: CollectionAction[_T]) -> None: if action.is_permutation_only: return if isinstance(action, DeltasAction): @@ -111,7 +110,7 @@ def _on_action(self, action: CollectionAction[_T]): elif isinstance(action, ClearAction): self._set_value(self._initial) - def _set_value(self, value: _S): + def _set_value(self, value: _S) -> None: if self._value != value: old_value = self._value self._value = value @@ -129,7 +128,7 @@ def observable(self) -> BiObservable[_S, _S]: @property @override - def derived_from(self) -> frozenset[Value]: + def derived_from(self) -> frozenset[Derived]: return EMPTY_FROZEN_SET @@ -154,7 +153,7 @@ def value_iter(self) -> Iterator[_S]: class CombinedValue(Value[_S], Generic[_S]): - def __init__(self, collection: ObservableCollection[_T], combiner: Callable[[Iterable[_T]], _S]): + def __init__(self, collection: ObservableCollection[_T], combiner: Callable[[Iterable[_T]], _S]) -> None: super().__init__() self._collection = collection self._combiner = combiner @@ -167,7 +166,7 @@ def __init__(self, collection: ObservableCollection[_T], combiner: Callable[[Ite def observable(self) -> BiObservable[_S, _S]: return self._on_change - def _recalculate_value(self): + def _recalculate_value(self) -> None: old_value = self._value self._value = self._combiner(self._collection) if self._value != old_value: @@ -180,5 +179,5 @@ def value(self) -> _S: @property @override - def derived_from(self) -> frozenset[Value]: + def derived_from(self) -> frozenset[Derived]: return EMPTY_FROZEN_SET diff --git a/src/spellbind/observable_sequences.py b/src/spellbind/observable_sequences.py index 4b770cd..5952be3 100644 --- a/src/spellbind/observable_sequences.py +++ b/src/spellbind/observable_sequences.py @@ -37,7 +37,7 @@ def on_change(self) -> ValueObservable[AtIndicesDeltasAction[_S_co] | ClearActio def map(self, transformer: Callable[[_S_co], _T]) -> ObservableSequence[_T]: ... @override - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, Sequence): return NotImplemented if len(self) != len(other): @@ -78,12 +78,12 @@ def as_raw_list(self) -> list[_S]: return [value.value for value in self] @override - def __str__(self): + def __str__(self) -> str: return "[" + ", ".join(str(value) for value in self) + "]" class UnboxedValueSequence(ObservableSequence[_S_co], Generic[_S_co]): - def __init__(self, value_sequence: ValueSequence[_S_co]): + def __init__(self, value_sequence: ValueSequence[_S_co]) -> None: self._value_sequence = value_sequence self._on_change = value_sequence.on_value_change self._delta_observable = value_sequence.value_delta_observable @@ -97,13 +97,13 @@ def __getitem__(self, index: int) -> _S_co: ... def __getitem__(self, index: slice) -> Sequence[_S_co]: ... @override - def __getitem__(self, index): + def __getitem__(self, index: int | slice) -> _S_co | Sequence[_S_co]: if isinstance(index, slice): return [value.value for value in self._value_sequence[index]] return self._value_sequence[index].value @override - def __iter__(self): + def __iter__(self) -> Iterator[_S_co]: return self._value_sequence.value_iter() @property @@ -126,11 +126,11 @@ def map(self, transformer: Callable[[_S_co], _T]) -> ObservableSequence[_T]: raise NotImplementedError @override - def __repr__(self): + def __repr__(self) -> str: return f"{self.__class__.__name__}({self})" @override - def __str__(self): + def __str__(self) -> str: return str(self._value_sequence) @@ -145,23 +145,23 @@ class MutableIndexObservableSequence(IndexObservableSequence[_S], MutableSequenc class MutableValueSequence(MutableObservableSequence[Value[_S]], Generic[_S], ABC): @abstractmethod @override - def append(self, item: _S | Value[_S]): ... + def append(self, item: _S | Value[_S]) -> None: ... @abstractmethod @override - def extend(self, items: Iterable[_S | Value[_S]]): ... + def extend(self, items: Iterable[_S | Value[_S]]) -> None: ... @abstractmethod @override - def insert(self, index: SupportsIndex, item: _S | Value[_S]): ... + def insert(self, index: SupportsIndex, item: _S | Value[_S]) -> None: ... @abstractmethod @override - def __delitem__(self, key: SupportsIndex | slice): ... + def __delitem__(self, key: SupportsIndex | slice) -> None: ... @abstractmethod @override - def clear(self): ... + def clear(self) -> None: ... @abstractmethod @override @@ -170,16 +170,16 @@ def pop(self, index: SupportsIndex = -1) -> Value[_S]: ... @overload @abstractmethod @override - def __setitem__(self, key: int, value: _S | Value[_S]): ... + def __setitem__(self, key: int, value: _S | Value[_S]) -> None: ... @overload @abstractmethod @override - def __setitem__(self, key: slice, value: Iterable[_S | Value[_S]]): ... + def __setitem__(self, key: slice, value: Iterable[_S | Value[_S]]) -> None: ... @abstractmethod @override - def __setitem__(self, key, value): ... + def __setitem__(self, key: int | slice, value: _S | Value[_S] | Iterable[_S | Value[_S]]) -> None: ... class ValueChangedMultipleTimesAction(ElementsChangedAction[_S_co], Generic[_S_co], ABC): @@ -242,7 +242,7 @@ def __eq__(self, other: object) -> bool: self.count == other.count) @override - def __repr__(self): + def __repr__(self) -> str: return f"{self.__class__.__name__}(new_item={self.new_item!r}, old_item={self.old_item!r}, count={self.count})" @@ -275,10 +275,10 @@ def __getitem__(self, index: SupportsIndex) -> _S: ... def __getitem__(self, index: slice) -> MutableSequence[_S]: ... @override - def __getitem__(self, index): + def __getitem__(self, index: SupportsIndex | slice) -> _S | MutableSequence[_S]: return self._values[index] - def _append(self, item: _S): + def _append(self, item: _S) -> None: self._values.append(item) new_length = len(self._values) if self.is_observed(): @@ -289,10 +289,10 @@ def _append(self, item: _S): else: self._len_value.value = new_length - def is_observed(self): + def is_observed(self) -> bool: return self._action_event.is_observed() or self._deltas_event.is_observed() - def _extend(self, items: Iterable[_S]): + def _extend(self, items: Iterable[_S]) -> None: old_length = len(self._values) observed = self.is_observed() if observed: @@ -311,7 +311,7 @@ def _extend(self, items: Iterable[_S]): else: self._len_value.value = new_length - def _insert(self, index: SupportsIndex, item: _S): + def _insert(self, index: SupportsIndex, item: _S) -> None: self._values.insert(index, item) if self.is_observed(): with self._len_value.set_delay_notify(len(self._values)): @@ -321,7 +321,7 @@ def _insert(self, index: SupportsIndex, item: _S): else: self._len_value.value = len(self._values) - def _insert_all(self, index_with_items: Iterable[tuple[int, _S]]): + def _insert_all(self, index_with_items: Iterable[tuple[int, _S]]) -> None: index_with_items = tuple(index_with_items) sorted_index_with_items = tuple(sorted(index_with_items, key=lambda x: x[0])) old_length = len(self._values) @@ -339,17 +339,17 @@ def _insert_all(self, index_with_items: Iterable[tuple[int, _S]]): else: self._len_value.value = new_length - def _remove(self, item: _S): + def _remove(self, item: _S) -> None: index = self.index(item) self._delitem_index(index) - def _delitem(self, key: SupportsIndex | slice): + def _delitem(self, key: SupportsIndex | slice) -> None: if isinstance(key, slice): self._delitem_slice(key) else: self._delitem_index(key) - def _delitem_index(self, key: SupportsIndex): + def _delitem_index(self, key: SupportsIndex) -> None: index = key.__index__() item = self[index] self._values.__delitem__(index) @@ -361,7 +361,7 @@ def _delitem_index(self, key: SupportsIndex): else: self._len_value.value = len(self._values) - def _delitem_slice(self, slice_key: slice): + def _delitem_slice(self, slice_key: slice) -> None: indices = range(*slice_key.indices(len(self._values))) if len(indices) == 0: return @@ -375,7 +375,7 @@ def indices_of(self, items: Iterable[_S]) -> Iterable[int]: last_indices[item] = index + 1 yield index - def _del_all(self, indices: Iterable[SupportsIndex]): + def _del_all(self, indices: Iterable[SupportsIndex]) -> None: indices_ints: tuple[int, ...] = tuple(index.__index__() for index in indices) if len(indices_ints) == 0: return @@ -391,11 +391,11 @@ def _del_all(self, indices: Iterable[SupportsIndex]): else: self._len_value.value = len(self._values) - def _remove_all(self, items: Iterable[_S]): + def _remove_all(self, items: Iterable[_S]) -> None: indices_to_remove = list(self.indices_of(items)) self._del_all(indices_to_remove) - def _clear(self): + def _clear(self) -> None: if self._deltas_event.is_observed(): removed_elements_with_index = tuple((enumerate(self))) else: @@ -417,18 +417,20 @@ def _pop(self, index: SupportsIndex = -1) -> _S: return item @overload - def _setitem(self, key: SupportsIndex, value: _S): ... + def _setitem(self, key: SupportsIndex, value: _S) -> None: ... @overload - def _setitem(self, key: slice, value: Iterable[_S]): ... + def _setitem(self, key: slice, value: Iterable[_S]) -> None: ... - def _setitem(self, key, value): + def _setitem(self, key: SupportsIndex | slice, value: _S | Iterable[_S]) -> None: if isinstance(key, slice): - self._setitem_slice(key, value) + # mypy does not understand the connection between key and value as it could be inferred from the overloads + self._setitem_slice(key, value) # type: ignore[arg-type] else: - self._setitem_index(key, value) + # mypy does not understand the connection between key and value as it could be inferred from the overloads + self._setitem_index(key, value) # type: ignore[arg-type] - def _setitem_slice(self, key: slice, values: Iterable[_S]): + def _setitem_slice(self, key: slice, values: Iterable[_S]) -> None: action: AtIndicesDeltasAction[_S] | None = None if self.is_observed(): old_length = len(self._values) @@ -455,7 +457,7 @@ def _setitem_slice(self, key: slice, values: Iterable[_S]): else: self._len_value.value = len(self._values) - def _setitem_index(self, key: SupportsIndex, value: _S): + def _setitem_index(self, key: SupportsIndex, value: _S) -> None: index = key.__index__() old_value = self[index] self._values.__setitem__(index, value) @@ -466,10 +468,10 @@ def _setitem_index(self, key: SupportsIndex, value: _S): self._deltas_event(action) @override - def __eq__(self, other): + def __eq__(self, other: object) -> bool: return self._values.__eq__(other) - def _iadd(self, values: Iterable[_S]) -> Self: # type: ignore[override, misc] + def _iadd(self, values: Iterable[_S]) -> Self: self._extend(values) return self @@ -492,7 +494,7 @@ def _imul(self, value: SupportsIndex) -> Self: self._extend(extend_by) return self - def _reverse(self): + def _reverse(self) -> None: if self.length_value.value < 2: return if self._deltas_event.is_observed(): @@ -513,26 +515,26 @@ def length_value(self) -> IntValue: return self._len_value @override - def __str__(self): + def __str__(self) -> str: return str(self._values) @override - def __repr__(self): + def __repr__(self) -> str: return f"{self.__class__.__name__}({self._values!r})" class _ValueCell(Generic[_S]): - def __init__(self, value: Value[_S], event: ValueEvent[ValueChangedMultipleTimesAction[_S]]): + def __init__(self, value: Value[_S], event: ValueEvent[ValueChangedMultipleTimesAction[_S]]) -> None: self._value = value self._count = 0 self._action_event = event - def on_added(self): + def on_added(self) -> None: self._count += 1 if self._count == 1: self._value.observe(self._on_value_changed) - def on_removed(self): + def on_removed(self) -> None: self._count -= 1 if self._count == 0: self._value.unobserve(self._on_value_changed) @@ -541,7 +543,7 @@ def on_removed(self): def count(self) -> int: return self._count - def _on_value_changed(self, new_value: _S, old_value: _S): + def _on_value_changed(self, new_value: _S, old_value: _S) -> None: self._action_event.emit_lazy(lambda: SimpleValueChangedMultipleTimesAction(new_item=new_value, old_item=old_value, count=self.count)) @@ -580,13 +582,13 @@ def on_value_change(self) -> ValueObservable[AtIndicesDeltasAction[_S] | ClearAc def value_delta_observable(self) -> ValuesObservable[DeltaAction[_S]]: return self._final_on_value_delta_action - def _on_value_sequence_delta(self, action: DeltaAction[Value[_S]]): + def _on_value_sequence_delta(self, action: DeltaAction[Value[_S]]) -> None: if action.is_add: self._on_value_added(action.value) else: self._on_value_removed(action.value) - def _on_value_added(self, value: Value[_S]): + def _on_value_added(self, value: Value[_S]) -> None: try: _ = value.constant_value_or_raise return @@ -599,7 +601,7 @@ def _on_value_added(self, value: Value[_S]): self._cells[value] = cell cell.on_added() - def _on_value_removed(self, value: Value[_S]): + def _on_value_removed(self, value: Value[_S]) -> None: try: _ = value.constant_value_or_raise return @@ -613,36 +615,36 @@ def _on_value_removed(self, value: Value[_S]): class ObservableList(IndexObservableSequenceBase[_S], MutableIndexObservableSequence[_S], Generic[_S]): @override - def append(self, item: _S): + def append(self, item: _S) -> None: self._append(item) @override - def extend(self, items: Iterable[_S]): + def extend(self, items: Iterable[_S]) -> None: self._extend(items) @override - def insert(self, index: SupportsIndex, item: _S): + def insert(self, index: SupportsIndex, item: _S) -> None: self._insert(index, item) - def insert_all(self, items_with_index: Iterable[tuple[int, _S]]): + def insert_all(self, items_with_index: Iterable[tuple[int, _S]]) -> None: self._insert_all(items_with_index) @override - def remove(self, item: _S): + def remove(self, item: _S) -> None: self._remove(item) @override - def __delitem__(self, key: SupportsIndex | slice): + def __delitem__(self, key: SupportsIndex | slice) -> None: self._delitem(key) - def del_all(self, indices: Iterable[SupportsIndex]): + def del_all(self, indices: Iterable[SupportsIndex]) -> None: self._del_all(indices) - def remove_all(self, items: Iterable[_S]): + def remove_all(self, items: Iterable[_S]) -> None: self._remove_all(items) @override - def clear(self): + def clear(self) -> None: self._clear() @override @@ -651,18 +653,19 @@ def pop(self, index: SupportsIndex = -1) -> _S: @overload @override - def __setitem__(self, key: SupportsIndex, value: _S): ... + def __setitem__(self, key: SupportsIndex, value: _S) -> None: ... @overload @override - def __setitem__(self, key: slice, value: Iterable[_S]): ... + def __setitem__(self, key: slice, value: Iterable[_S]) -> None: ... @override - def __setitem__(self, key, value): - self._setitem(key, value) + def __setitem__(self, key: SupportsIndex | slice, value: _S | Iterable[_S]) -> None: + # mypy does not understand the connection between key and value as it could be inferred from the overloads + self._setitem(key, value) # type: ignore[arg-type] @override - def __iadd__(self, values: Iterable[_S]) -> Self: # type: ignore[override, misc] + def __iadd__(self, values: Iterable[_S]) -> Self: return self._iadd(values) def __imul__(self, value: SupportsIndex) -> Self: @@ -672,42 +675,42 @@ def __mul__(self, other: SupportsIndex) -> MutableSequence[_S]: return self._mul(other) @override - def reverse(self): + def reverse(self) -> None: self._reverse() class ValueList(ValueSequenceBase[_S], MutableIndexObservableSequence[Value[_S]], Generic[_S], ABC): @override - def append(self, item: Value[_S]): + def append(self, item: Value[_S]) -> None: self._append(item) @override - def extend(self, items: Iterable[Value[_S]]): + def extend(self, items: Iterable[Value[_S]]) -> None: self._extend(items) @override - def insert(self, index: SupportsIndex, item: Value[_S]): + def insert(self, index: SupportsIndex, item: Value[_S]) -> None: self._insert(index, item) - def insert_all(self, items_with_index: Iterable[tuple[int, Value[_S]]]): + def insert_all(self, items_with_index: Iterable[tuple[int, Value[_S]]]) -> None: self._insert_all(items_with_index) @override - def remove(self, item: Value[_S]): + def remove(self, item: Value[_S]) -> None: self._remove(item) @override - def __delitem__(self, key: SupportsIndex | slice): + def __delitem__(self, key: SupportsIndex | slice) -> None: self._delitem(key) - def del_all(self, indices: Iterable[SupportsIndex]): + def del_all(self, indices: Iterable[SupportsIndex]) -> None: self._del_all(indices) - def remove_all(self, items: Iterable[Value[_S]]): + def remove_all(self, items: Iterable[Value[_S]]) -> None: self._remove_all(items) @override - def clear(self): + def clear(self) -> None: self._clear() @override @@ -716,18 +719,19 @@ def pop(self, index: SupportsIndex = -1) -> Value[_S]: @overload @override - def __setitem__(self, key: SupportsIndex, value: Value[_S]): ... + def __setitem__(self, key: SupportsIndex, value: Value[_S]) -> None: ... @overload @override - def __setitem__(self, key: slice, value: Iterable[Value[_S]]): ... + def __setitem__(self, key: slice, value: Iterable[Value[_S]]) -> None: ... @override - def __setitem__(self, key, value): - self._setitem(key, value) + def __setitem__(self, key: SupportsIndex | slice, value: Value[_S] | Iterable[Value[_S]]) -> None: + # mypy does not understand the connection between key and value as it could be inferred from the overloads + self._setitem(key, value) # type: ignore[arg-type] @override - def __iadd__(self, values: Iterable[Value[_S]]) -> Self: # type: ignore[override, misc] + def __iadd__(self, values: Iterable[Value[_S]]) -> Self: return self._iadd(values) def __imul__(self, value: SupportsIndex) -> Self: @@ -737,7 +741,7 @@ def __mul__(self, other: SupportsIndex) -> MutableSequence[Value[_S]]: return self._mul(other) @override - def reverse(self): + def reverse(self) -> None: self._reverse() @@ -773,11 +777,11 @@ def __init__(self, values: Iterable[_S | Value[_S]] | None = None, *, super().__init__(_to_values(values, checker, constant_factory)) @override - def append(self, item: _S | Value[_S]): + def append(self, item: _S | Value[_S]) -> None: super().append(_to_value(item, self._checker, self._constant_factory)) @override - def extend(self, items: Iterable[_S | Value[_S]]): + def extend(self, items: Iterable[_S | Value[_S]]) -> None: super().extend(_to_values(items, self._checker, self._constant_factory)) @override @@ -786,37 +790,39 @@ def __iadd__(self, values: Iterable[_S | Value[_S]]) -> Self: return self @override - def insert(self, index: SupportsIndex, item: _S | Value[_S]): + def insert(self, index: SupportsIndex, item: _S | Value[_S]) -> None: super().insert(index, _to_value(item, self._checker, self._constant_factory)) @override - def insert_all(self, items_with_index: Iterable[tuple[int, _S | Value[_S]]]): + def insert_all(self, items_with_index: Iterable[tuple[int, _S | Value[_S]]]) -> None: super().insert_all(_with_indices_to_values_with_indices(items_with_index, self._checker, self._constant_factory)) @override - def remove(self, item: _S | Value[_S]): + def remove(self, item: _S | Value[_S]) -> None: super().remove(_to_value(item, self._checker, self._constant_factory)) @override - def remove_all(self, items: Iterable[_S | Value[_S]]): + def remove_all(self, items: Iterable[_S | Value[_S]]) -> None: super().remove_all(_to_values(items, self._checker, self._constant_factory)) @overload @override - def __setitem__(self, key: SupportsIndex, value: _S | Value[_S]): ... + def __setitem__(self, key: SupportsIndex, value: _S | Value[_S]) -> None: ... @overload @override - def __setitem__(self, key: slice, value: Iterable[_S | Value[_S]]): ... + def __setitem__(self, key: slice, value: Iterable[_S | Value[_S]]) -> None: ... @override - def __setitem__(self, key, value): + def __setitem__(self, key: SupportsIndex | slice, value: _S | Value[_S] | Iterable[_S | Value[_S]]) -> None: if isinstance(key, slice): - self._setitem_slice(key, _to_values(value, self._checker, self._constant_factory)) + # mypy does not understand the connection between key and value as it could be inferred from the overloads + self._setitem_slice(key, _to_values(value, self._checker, self._constant_factory)) # type: ignore[arg-type] else: - self._setitem_index(key, _to_value(value, self._checker, self._constant_factory)) + # mypy does not understand the connection between key and value as it could be inferred from the overloads + self._setitem_index(key, _to_value(value, self._checker, self._constant_factory)) # type: ignore[arg-type] - def _compare_value(self, self_value: Value[int], other_value: Value[int] | int) -> bool: + def _compare_value(self, self_value: Value[_S], other_value: Value[_S] | _S) -> bool: if self_value == other_value: return True if self._checker(other_value): @@ -829,7 +835,7 @@ def _compare_value(self, self_value: Value[int], other_value: Value[int] | int) return False @override - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, Sequence): return NotImplemented if len(self) != len(other): @@ -841,12 +847,12 @@ def __eq__(self, other): class MappedIndexObservableSequence(IndexObservableSequenceBase[_S], Generic[_S]): - def __init__(self, mapped_from: IndexObservableSequence[_T], map_func: Callable[[_T], _S]): + def __init__(self, mapped_from: IndexObservableSequence[_T], map_func: Callable[[_T], _S]) -> None: super().__init__(map_func(item) for item in mapped_from) self._mapped_from = mapped_from self._map_func = map_func - def on_action(other_action: AtIndicesDeltasAction[_T] | ClearAction[_T] | ReverseAction[_T]): + def on_action(other_action: AtIndicesDeltasAction[_T] | ClearAction[_T] | ReverseAction[_T]) -> None: if isinstance(other_action, AtIndicesDeltasAction): if isinstance(other_action, ExtendAction): self._extend((self._map_func(item) for item in other_action.items)) @@ -871,12 +877,12 @@ def on_action(other_action: AtIndicesDeltasAction[_T] | ClearAction[_T] | Revers mapped_from.on_change.observe(on_action) - def _is_observed(self): + def _is_observed(self) -> bool: return self._action_event.is_observed() or self._deltas_event.is_observed() @property @override - def on_change(self) -> ValueObservable[AtIndicesDeltasAction[_S] | ClearAction | ReverseAction]: + def on_change(self) -> ValueObservable[AtIndicesDeltasAction[_S] | ClearAction[_S] | ReverseAction[_S]]: return self._action_event @property @@ -902,14 +908,14 @@ def __getitem__(self, index: SupportsIndex) -> _S: ... def __getitem__(self, index: slice) -> MutableSequence[_S]: ... @override - def __getitem__(self, index): + def __getitem__(self, index: SupportsIndex | slice) -> _S | MutableSequence[_S]: return self._values[index] class _EmptyObservableSequence(IndexObservableSequence[_S], Generic[_S]): @property @override - def on_change(self) -> ValueObservable[AtIndicesDeltasAction[_S] | ClearAction | ReverseAction]: + def on_change(self) -> ValueObservable[AtIndicesDeltasAction[_S] | ClearAction[_S] | ReverseAction[_S]]: return void_value_observable() @property @@ -951,7 +957,7 @@ def __getitem__(self, index: int) -> _S: ... def __getitem__(self, index: slice) -> Sequence[_S]: ... @override - def __getitem__(self, index): + def __getitem__(self, index: object) -> _S | Sequence[_S]: raise IndexError("Empty sequence has no items") @override @@ -959,16 +965,16 @@ def __iter__(self) -> Iterator[_S]: return iter(()) @override - def __contains__(self, item) -> bool: + def __contains__(self, item: object) -> bool: return False @override - def __str__(self): + def __str__(self) -> str: return "[]" -EMPTY_SEQUENCE: IndexObservableSequence = _EmptyObservableSequence() +EMPTY_SEQUENCE: IndexObservableSequence[Any] = _EmptyObservableSequence() def empty_sequence() -> IndexObservableSequence[_S]: - return EMPTY_SEQUENCE # type: ignore[return-value] + return EMPTY_SEQUENCE diff --git a/src/spellbind/observables.py b/src/spellbind/observables.py index 31694e3..5bca139 100644 --- a/src/spellbind/observables.py +++ b/src/spellbind/observables.py @@ -11,7 +11,7 @@ _S_contra = TypeVar("_S_contra", contravariant=True) _T_contra = TypeVar("_T_contra", contravariant=True) _U_contra = TypeVar("_U_contra", contravariant=True) -_I_contra = TypeVar("_I_contra", bound=Iterable, contravariant=True) +_I_contra = TypeVar("_I_contra", bound=Iterable[Any], contravariant=True) _S_co = TypeVar("_S_co", covariant=True) _T_co = TypeVar("_T_co", covariant=True) @@ -22,10 +22,10 @@ _U = TypeVar("_U") _V = TypeVar("_V") _W = TypeVar("_W") -_I = TypeVar("_I", bound=Iterable) +_I = TypeVar("_I", bound=Iterable[Any]) -_O = TypeVar('_O', bound=Callable) +_O = TypeVar('_O', bound=Callable[..., Any]) class Observer(Protocol): @@ -61,7 +61,7 @@ class DeadReferenceError(RemoveSubscriptionError): class Subscription(ABC): - def __init__(self, observer: Callable, times: int | None, on_silent_change: Callable[[bool], None]): + def __init__(self, observer: Callable[..., Any], times: int | None, on_silent_change: Callable[[bool], None]) -> None: self._positional_parameter_count = -1 if not has_var_args(observer): self._positional_parameter_count = count_positional_parameters(observer) @@ -70,7 +70,7 @@ def __init__(self, observer: Callable, times: int | None, on_silent_change: Call self._silent = False self._on_silent_change = on_silent_change - def _call(self, observer: Callable, *args) -> None: + def _call(self, observer: Callable[..., Any], *args: Any) -> None: if not self._silent: self._call_counter += 1 if self._positional_parameter_count == -1: @@ -100,46 +100,46 @@ def silent(self, value: bool) -> None: self._on_silent_change(value) @abstractmethod - def __call__(self, *args) -> None: ... + def __call__(self, *args: Any) -> None: ... @abstractmethod - def matches_observer(self, observer: Callable) -> bool: ... + def matches_observer(self, observer: Callable[..., Any]) -> bool: ... class StrongSubscription(Subscription): - def __init__(self, observer: Callable, times: int | None, on_silent_change: Callable[[bool], None]): + def __init__(self, observer: Callable[..., Any], times: int | None, on_silent_change: Callable[[bool], None]) -> None: super().__init__(observer, times, on_silent_change) self._observer = observer @override - def __call__(self, *args) -> None: + def __call__(self, *args: Any) -> None: self._call(self._observer, *args) @override - def matches_observer(self, observer: Callable) -> bool: + def matches_observer(self, observer: Callable[..., Any]) -> bool: return self._observer == observer class StrongManyToOneSubscription(Subscription): - def __init__(self, observer: Callable, times: int | None, on_silent_change: Callable[[bool], None]): + def __init__(self, observer: Callable[..., Any], times: int | None, on_silent_change: Callable[[bool], None]) -> None: super().__init__(observer, times, on_silent_change) self._observer = observer @override - def __call__(self, *args_args) -> None: + def __call__(self, *args_args: Any) -> None: for args in args_args: for v in args: self._call(self._observer, v) @override - def matches_observer(self, observer: Callable) -> bool: + def matches_observer(self, observer: Callable[..., Any]) -> bool: return self._observer == observer class WeakSubscription(Subscription): - _ref: ref[Callable] | WeakMethod + _ref: ref[Callable[..., Any]] | WeakMethod[Callable[..., Any]] - def __init__(self, observer: Callable, times: int | None, on_silent_change: Callable[[bool], None]): + def __init__(self, observer: Callable[..., Any], times: int | None, on_silent_change: Callable[[bool], None]) -> None: super().__init__(observer, times, on_silent_change) if hasattr(observer, '__self__'): self._ref = WeakMethod(observer) @@ -147,21 +147,21 @@ def __init__(self, observer: Callable, times: int | None, on_silent_change: Call self._ref = ref(observer) @override - def __call__(self, *args) -> None: + def __call__(self, *args: Any) -> None: observer = self._ref() if observer is None: raise DeadReferenceError() self._call(observer, *args) @override - def matches_observer(self, observer: Callable) -> bool: + def matches_observer(self, observer: Callable[..., Any]) -> bool: return self._ref() == observer class WeakManyToOneSubscription(Subscription): - _ref: ref[Callable] | WeakMethod + _ref: ref[Callable[..., Any]] | WeakMethod[Callable[..., Any]] - def __init__(self, observer: Callable, times: int | None, on_silent_change: Callable[[bool], None]): + def __init__(self, observer: Callable[..., Any], times: int | None, on_silent_change: Callable[[bool], None]) -> None: super().__init__(observer, times, on_silent_change) if hasattr(observer, '__self__'): self._ref = WeakMethod(observer) @@ -169,7 +169,7 @@ def __init__(self, observer: Callable, times: int | None, on_silent_change: Call self._ref = ref(observer) @override - def __call__(self, *args_args) -> None: + def __call__(self, *args_args: Any) -> None: observer = self._ref() if observer is None: raise DeadReferenceError() @@ -178,7 +178,7 @@ def __call__(self, *args_args) -> None: self._call(observer, v) @override - def matches_observer(self, observer: Callable) -> bool: + def matches_observer(self, observer: Callable[..., Any]) -> bool: return self._ref() == observer @@ -324,7 +324,7 @@ def or_values(self, other: ValuesObservable[_T]) -> ValuesObservable[_S_co | _T] class _BaseObservable(Generic[_O], ABC): _subscriptions: list[Subscription] - def __init__(self): + def __init__(self) -> None: super().__init__() self._subscriptions = [] self._active_subscription_count = 0 @@ -350,12 +350,12 @@ def weak_observe(self, observer: _O, times: int | None = None) -> Subscription: self._append_subscription(subscription) return subscription - def _append_subscription(self, subscription: Subscription): + def _append_subscription(self, subscription: Subscription) -> None: self._subscriptions.append(subscription) if not subscription.silent: self._active_subscription_count += 1 - def _del_subscription(self, index: int): + def _del_subscription(self, index: int) -> None: sub = self._subscriptions.pop(index) if not sub.silent: self._active_subscription_count -= 1 @@ -425,7 +425,7 @@ def _get_parameter_count(self) -> int: class _DerivedObservableBase(_BaseObservable[_O], Generic[_O], ABC): - def __init__(self, weakly: bool, transformer: Callable | None, predicate: Callable | None): + def __init__(self, weakly: bool, transformer: Callable[..., Any] | None, predicate: Callable[..., Any] | None) -> None: super().__init__() if transformer is None: @@ -436,12 +436,12 @@ def transformer(*args: Any) -> Any: self._weakly = weakly @override - def _append_subscription(self, subscription: Subscription): + def _append_subscription(self, subscription: Subscription) -> None: super()._append_subscription(subscription) self._set_subscriptions_silent(False) @override - def _del_subscription(self, index: int): + def _del_subscription(self, index: int) -> None: super()._del_subscription(index) if not self.is_observed(): self._set_subscriptions_silent(True) @@ -455,16 +455,16 @@ def _on_subscription_silent_change(self, silent: bool) -> None: else: self._set_subscriptions_silent(False) - def _on_derived_emit(self, *values) -> None: + def _on_derived_emit(self, *values: Any) -> None: if self._predicate is None or self._predicate(*values): self._emit_n_lazy(lambda: self._transformer(*values)) @abstractmethod - def _set_subscriptions_silent(self, silent: bool): ... + def _set_subscriptions_silent(self, silent: bool) -> None: ... class _DerivedFromOneObservableBase(_DerivedObservableBase[_O], Generic[_O], ABC): - def __init__(self, derived_from: Observable, transformer: Callable, weakly: bool, predicate: Callable | None): + def __init__(self, derived_from: Observable, transformer: Callable[..., Any], weakly: bool, predicate: Callable[..., Any] | None) -> None: super().__init__(weakly, transformer, predicate) self._derived_from = derived_from @@ -475,12 +475,14 @@ def __init__(self, derived_from: Observable, transformer: Callable, weakly: bool self._set_subscriptions_silent(True) @override - def _set_subscriptions_silent(self, silent: bool): + def _set_subscriptions_silent(self, silent: bool) -> None: self._derived_from_subscription.silent = silent class _DerivedFromManyObservableBase(_DerivedObservableBase[_O], Generic[_O], ABC): - def __init__(self, derived_from: Iterable[Observable], weakly: bool, transformer: Callable | None, predicate: Callable | None): + def __init__(self, derived_from: Iterable[Observable], weakly: bool, + transformer: Callable[..., Any] | None, + predicate: Callable[..., Any] | None) -> None: super().__init__(weakly, transformer=transformer, predicate=predicate) self._derived_from = derived_from if self._weakly: @@ -490,7 +492,7 @@ def __init__(self, derived_from: Iterable[Observable], weakly: bool, transformer self._set_subscriptions_silent(True) @override - def _set_subscriptions_silent(self, silent: bool): + def _set_subscriptions_silent(self, silent: bool) -> None: for sub in self._derived_from_subscriptions: sub.silent = silent @@ -633,7 +635,7 @@ def __init__(self, super().__init__(derived_from, transformer, weakly, predicate) @override - def _on_derived_emit(self, values) -> None: + def _on_derived_emit(self, values: Any) -> None: predicate = self._predicate if predicate is None: self._emit_single_lazy(lambda: tuple(self._transformer(value) for value in values)) @@ -657,7 +659,7 @@ def _get_parameter_count(self) -> int: return 1 @override - def _on_derived_emit(self, value) -> None: + def _on_derived_emit(self, value: _S) -> None: if self._predicate is None or self._predicate(value): self._emit_single_lazy(lambda: self._transformer(value)) @@ -704,7 +706,7 @@ def __init__(self, super().__init__(derived_from, transformer, weakly, predicate) @override - def _on_derived_emit(self, value) -> None: + def _on_derived_emit(self, value: Any) -> None: predicate = self._predicate if predicate is None or predicate(value): self._emit_single_lazy(lambda: self._transformer(value)) @@ -758,15 +760,15 @@ def _get_parameter_count(self) -> int: class _VoidSubscription(Subscription): - def __init__(self): + def __init__(self) -> None: super().__init__(lambda: None, None, lambda silent: None) @override - def __call__(self, *args) -> None: + def __call__(self, *args: Any) -> None: pass # pragma: no cover @override - def matches_observer(self, observer: Callable) -> bool: + def matches_observer(self, observer: Callable[..., Any]) -> bool: return False # pragma: no cover @@ -875,28 +877,28 @@ def is_observed(self, by: Observer | ValuesObserver[_S] | None = None) -> bool: return False # pragma: no cover -VOID_OBSERVABLE: Observable = _VoidObservable() -VOID_VALUE_OBSERVABLE: ValueObservable = _VoidValueObservable() -VOID_BI_OBSERVABLE: BiObservable = _VoidBiObservable() -VOID_TRI_OBSERVABLE: TriObservable = _VoidTriObservable() -VOID_VALUES_OBSERVABLE: ValuesObservable = _VoidValuesObservable() +_VOID_OBSERVABLE: Observable = _VoidObservable() +_VOID_VALUE_OBSERVABLE: ValueObservable[Any] = _VoidValueObservable() +_VOID_BI_OBSERVABLE: BiObservable[Any, Any] = _VoidBiObservable() +_VOID_TRI_OBSERVABLE: TriObservable[Any, Any, Any] = _VoidTriObservable() +_VOID_VALUES_OBSERVABLE: ValuesObservable[Iterable[Any]] = _VoidValuesObservable() def void_observable() -> Observable: - return VOID_OBSERVABLE + return _VOID_OBSERVABLE def void_value_observable() -> ValueObservable[_S]: - return VOID_VALUE_OBSERVABLE + return _VOID_VALUE_OBSERVABLE def void_bi_observable() -> BiObservable[_S, _T]: - return VOID_BI_OBSERVABLE + return _VOID_BI_OBSERVABLE def void_tri_observable() -> TriObservable[_S, _T, _U]: - return VOID_TRI_OBSERVABLE + return _VOID_TRI_OBSERVABLE def void_values_observable() -> ValuesObservable[_S]: - return VOID_VALUES_OBSERVABLE + return _VOID_VALUES_OBSERVABLE # type: ignore[return-value] diff --git a/src/spellbind/str_collections.py b/src/spellbind/str_collections.py index b76ae7e..4603898 100644 --- a/src/spellbind/str_collections.py +++ b/src/spellbind/str_collections.py @@ -27,7 +27,7 @@ class ObservableStrList(ObservableList[str], ObservableStrCollection): class CombinedStrValue(CombinedValue[str], StrValue): - def __init__(self, collection: ObservableCollection[_S], combiner: Callable[[Iterable[_S]], str]): + def __init__(self, collection: ObservableCollection[_S], combiner: Callable[[Iterable[_S]], str]) -> None: super().__init__(collection=collection, combiner=combiner) diff --git a/src/spellbind/str_values.py b/src/spellbind/str_values.py index 5125356..a1d1633 100644 --- a/src/spellbind/str_values.py +++ b/src/spellbind/str_values.py @@ -136,5 +136,5 @@ def create(transformer: Callable[[_S, _T, _U], str], class ToStrValue(OneToOneValue[Any, str], StrValue): - def __init__(self, value: Value[Any]): + def __init__(self, value: Value[Any]) -> None: super().__init__(str, value) diff --git a/src/spellbind/values.py b/src/spellbind/values.py index fb850e7..7930763 100644 --- a/src/spellbind/values.py +++ b/src/spellbind/values.py @@ -5,7 +5,7 @@ from abc import ABC, abstractmethod from contextlib import contextmanager from typing import TypeVar, Generic, Optional, Iterable, TYPE_CHECKING, Callable, Sequence, ContextManager, \ - Generator + Generator, Any from spellbind.deriveds import Derived from spellbind.event import BiEvent @@ -18,7 +18,7 @@ from spellbind.float_values import FloatValue # pragma: no cover -EMPTY_FROZEN_SET: frozenset = frozenset() +EMPTY_FROZEN_SET: frozenset[Any] = frozenset() _S = TypeVar("_S") _T = TypeVar("_T") @@ -58,7 +58,7 @@ def weak_observe(self, observer: Observer | ValueObserver[_S] | BiObserver[_S, _ def unobserve(self, observer: Observer | ValueObserver[_S] | BiObserver[_S, _S]) -> None: self.observable.unobserve(observer=observer) - def is_observed(self, by: Callable | None = None) -> bool: + def is_observed(self, by: Callable[..., Any] | None = None) -> bool: return self.observable.is_observed(by=by) def to_str(self) -> StrValue: @@ -88,7 +88,7 @@ def map_to_bool(self, transformer: Callable[[_S], bool]) -> BoolValue: def constant_value_or_raise(self) -> _S: raise NotConstantError - def decompose_operands(self, operator_: Callable) -> Sequence[Value[_S] | _S]: + def decompose_operands(self, operator_: Callable[..., Any]) -> Sequence[Value[_S] | _S]: return (self,) @override @@ -191,7 +191,7 @@ class SimpleVariable(Variable[_S], Generic[_S]): _on_change: BiEvent[_S, _S] _bound_to: Optional[Value[_S]] - def __init__(self, value: _S): + def __init__(self, value: _S) -> None: self._bound_to_set = EMPTY_FROZEN_SET self._value = value self._on_change = BiEvent[_S, _S]() @@ -278,7 +278,7 @@ def derived_from(self) -> frozenset[Value[_S]]: class Constant(Value[_S], Generic[_S]): _value: _S - def __init__(self, value: _S): + def __init__(self, value: _S) -> None: self._value = value @property @@ -315,18 +315,19 @@ def of(cls, value: _S) -> Constant[_S]: return Constant(value) @override - def __eq__(self, other): + def __eq__(self, other: object) -> bool: if not isinstance(other, Constant): return NotImplemented - return self._value == other._value + # mypy --strict complains that equality between two "Any" does return Any, not bool + return bool(self._value == other.constant_value_or_raise) @override - def __hash__(self): + def __hash__(self) -> int: return hash(self._value) class DerivedValueBase(Value[_S], Generic[_S], ABC): - def __init__(self, *derived_from: Value): + def __init__(self, *derived_from: Value[Any]): self._derived_from = frozenset(derived_from) self._on_change: BiEvent[_S, _S] = BiEvent[_S, _S]() for value in derived_from: @@ -340,7 +341,7 @@ def observable(self) -> BiObservable[_S, _S]: @property @override - def derived_from(self) -> frozenset[Value]: + def derived_from(self) -> frozenset[Derived]: return self._derived_from def _on_dependency_changed(self) -> None: @@ -362,7 +363,7 @@ def value(self) -> _S: class OneToOneValue(DerivedValueBase[_T], Generic[_S, _T]): _getter: Callable[[], _S] - def __init__(self, transformer: Callable[[_S], _T], of: Value[_S]): + def __init__(self, transformer: Callable[[_S], _T], of: Value[_S]) -> None: self._getter = _create_value_getter(of) self._of = of self._transformer = transformer @@ -388,7 +389,7 @@ def _calculate_value(self) -> _T: class ManyToSameValue(ManyToOneValue[_S, _S], Generic[_S]): @override - def decompose_operands(self, transformer: Callable) -> Sequence[Value[_S] | _S]: + def decompose_operands(self, transformer: Callable[..., _S]) -> Sequence[Value[_S] | _S]: if transformer == self._transformer: return self._input_values return (self,) @@ -437,7 +438,7 @@ def get_constant_of_generic_like(value: _S | Value[_S]) -> _S: return value -def decompose_operands_of_generic_like(operator_: Callable, value: _S | Value[_S]) -> Sequence[_S | Value[_S]]: +def decompose_operands_of_generic_like(operator_: Callable[..., _S], value: _S | Value[_S]) -> Sequence[_S | Value[_S]]: if isinstance(value, Value): return value.decompose_operands(operator_) return (value,) diff --git a/tests/test_collections/test_empty_sequence.py b/tests/test_collections/test_empty_sequence.py index 8fe263f..7ee43ac 100644 --- a/tests/test_collections/test_empty_sequence.py +++ b/tests/test_collections/test_empty_sequence.py @@ -2,7 +2,7 @@ from spellbind.observable_sequences import empty_sequence from spellbind.int_values import IntConstant -from spellbind.observables import VOID_VALUES_OBSERVABLE, VOID_VALUE_OBSERVABLE +from spellbind.observables import _VOID_VALUES_OBSERVABLE, _VOID_VALUE_OBSERVABLE def test_empty_sequence_str(): @@ -42,5 +42,5 @@ def test_empty_sequence_get_item_raises(): def test_empty_sequence_observers_are_void(): empty_seq = empty_sequence() - assert empty_seq.delta_observable is VOID_VALUES_OBSERVABLE - assert empty_seq.on_change is VOID_VALUE_OBSERVABLE + assert empty_seq.delta_observable is _VOID_VALUES_OBSERVABLE + assert empty_seq.on_change is _VOID_VALUE_OBSERVABLE