def __init__(self, default=undefined_value, preprocessor: Preprocessor = None, validator: Validator = None, class_observers: t.Iterable[Observer] = None): """ Params: default -- default value of the Observable. It is not validated (you are supposed to know what you are doing). Default: sentinel object "pca.data.observable.undefined_value" preprocessor -- a callable of signature: (raw_value) -> value which prepares what's going-to-be new value before the assignment and the validity checks. It's usable when you want to cast the value, instantiate a class to be a value or something similar. validator -- a callable of signature: (old_value) -> None which checks if the value is valid, whatever that means to the observable. class_observers -- iterable of per-class observers. With this argument you can declare the observers during Observable declaration. """ self.default = default self.preprocessor = preprocessor self.validator = validator self._class_observers = OrderedSet( class_observers) if class_observers else OrderedSet()
def test_binary_operations(): set1 = OrderedSet("abracadabra") set2 = OrderedSet("simsalabim") assert set1 != set2 assert set1 & set2 == OrderedSet(["a", "b"]) assert set1 | set2 == OrderedSet(["a", "b", "r", "c", "d", "s", "i", "m", "l"]) assert set1 - set2 == OrderedSet(["r", "c", "d"])
def test_binary_operations(): set1 = OrderedSet('abracadabra') set2 = OrderedSet('simsalabim') assert set1 != set2 assert set1 & set2 == OrderedSet(['a', 'b']) assert set1 | set2 == OrderedSet( ['a', 'b', 'r', 'c', 'd', 's', 'i', 'm', 'l']) assert set1 - set2 == OrderedSet(['r', 'c', 'd'])
def test_indexing(): set1 = OrderedSet("abracadabra") assert set1[:] == set1 assert set1.copy() == set1 assert set1[:] is set1 assert set1.copy() is not set1 assert set1[[1, 2]] == OrderedSet(["b", "r"]) assert set1[1:3] == OrderedSet(["b", "r"]) assert set1.index("b") == 1 assert set1.index(["b", "r"]) == [1, 2] with pytest.raises(KeyError): set1.index("br")
def test_indexing(): set1 = OrderedSet('abracadabra') assert set1[:] == set1 assert set1.copy() == set1 assert set1[:] is set1 assert set1.copy() is not set1 assert set1[[1, 2]] == OrderedSet(['b', 'r']) assert set1[1:3] == OrderedSet(['b', 'r']) assert set1.index('b') == 1 assert set1.index(['b', 'r']) == [1, 2] with pytest.raises(KeyError): set1.index('br')
def __init__( self, default=undefined_value, preprocessor: Preprocessor = None, validator: Validator = None, class_observers: t.Iterable[Observer] = None ): """ Params: default -- default value of the Observable. It is not validated (you are supposed to know what you are doing). Default: sentinel object "pca.data.observable.undefined_value" preprocessor -- a callable of signature: (raw_value) -> value which prepares what's going-to-be new value before the assignment and the validity checks. It's usable when you want to cast the value, instantiate a class to be a value or something similar. validator -- a callable of signature: (old_value) -> None which checks if the value is valid, whatever that means to the observable. class_observers -- iterable of per-class observers. With this argument you can declare the observers during Observable declaration. """ self.default = default self.preprocessor = preprocessor self.validator = validator self._class_observers = OrderedSet(class_observers) if class_observers else OrderedSet()
def test_pop(): set1 = OrderedSet("ab") elem = set1.pop() assert elem == "b" elem = set1.pop() assert elem == "a" with pytest.raises(KeyError): set1.pop()
def test_pop(): set1 = OrderedSet('ab') elem = set1.pop() assert elem == 'b' elem = set1.pop() assert elem == 'a' with pytest.raises(KeyError): set1.pop()
def test_remove(): set1 = OrderedSet('abracadabra') set1.remove('a') set1.remove('b') assert set1 == OrderedSet('rcd') assert set1[0] == 'r' assert set1[1] == 'c' assert set1[2] == 'd' assert set1.index('r') == 0 assert set1.index('c') == 1 assert set1.index('d') == 2 assert 'a' not in set1 assert 'b' not in set1 assert 'r' in set1 # Make sure we can .discard() something that's already gone, plus # something that was never there set1.discard('a') set1.discard('a')
def test_remove(): set1 = OrderedSet("abracadabra") set1.remove("a") set1.remove("b") assert set1 == OrderedSet("rcd") assert set1[0] == "r" assert set1[1] == "c" assert set1[2] == "d" assert set1.index("r") == 0 assert set1.index("c") == 1 assert set1.index("d") == 2 assert "a" not in set1 assert "b" not in set1 assert "r" in set1 # Make sure we can .discard() something that's already gone, plus # something that was never there set1.discard("a") set1.discard("a")
def test_update(): set1 = OrderedSet("abcd") result = set1.update("efgh") assert result == 7 assert len(set1) == 8 assert "".join(set1) == "abcdefgh" set2 = OrderedSet("abcd") result = set2.update("cdef") assert result == 5 assert len(set2) == 6 assert "".join(set2) == "abcdef"
def test_update(): set1 = OrderedSet('abcd') result = set1.update('efgh') assert result == 7 assert len(set1) == 8 assert ''.join(set1) == 'abcdefgh' set2 = OrderedSet('abcd') result = set2.update('cdef') assert result == 5 assert len(set2) == 6 assert ''.join(set2) == 'abcdef'
def add_instance_observer(self, instance: Owner, observer: Observer) -> None: """ Adds a function or method as a change observer on per-instance basis. Params: instance - the owner instance that the observer is connected to observer - a function or method of (owner_instance, old_value, new_value) -> None signature that is going to be called whenever the observable changes its value. It is supposed to serve as a observer of the observable's value. """ # we're asserting valid observer signature (without relying on # duck-typing), because observer is passed here, but the potential # TypeError is going to be raised much further during the runtime # TODO assert observer signature is valid assert is_argspec_valid(observer, arg_number=3) # per-instance change observers # observable signs itself with its label in the instance __dict__ key = self._observer_key_pattern % self._label instance.__dict__.setdefault(key, OrderedSet()).add(observer)
class Observable: _owner: Owner _label: str # pattern for __dict__ key on the owner class; space char is intended to be # sure we are not colliding with any proper attribute name _observer_key_pattern = '%s instance observers' def __init__(self, default=undefined_value, preprocessor: Preprocessor = None, validator: Validator = None, class_observers: t.Iterable[Observer] = None): """ Params: default -- default value of the Observable. It is not validated (you are supposed to know what you are doing). Default: sentinel object "pca.data.observable.undefined_value" preprocessor -- a callable of signature: (raw_value) -> value which prepares what's going-to-be new value before the assignment and the validity checks. It's usable when you want to cast the value, instantiate a class to be a value or something similar. validator -- a callable of signature: (old_value) -> None which checks if the value is valid, whatever that means to the observable. class_observers -- iterable of per-class observers. With this argument you can declare the observers during Observable declaration. """ self.default = default self.preprocessor = preprocessor self.validator = validator self._class_observers = OrderedSet( class_observers) if class_observers else OrderedSet() def __set_name__(self, owner, name): self._owner = owner self._label = name @property def label(self) -> str: """Python name of the attribute under which the observable is hung on the owner class.""" return self._label def _get_value(self, instance: Owner) -> Value: """Technical detail of retrieving the value from the instance `__dict__`.""" return instance.__dict__.get(self._label, self.default) def _set_value(self, instance: Owner, value: Value): """Technical detail of setting the value at the instance `__dict__`.""" instance.__dict__[self._label] = value def __get__(self, instance: Owner, owner: t.Type) -> Value: """ If accessed from the instance, gets the value of the observable from the owner's `__dict__`. If accessed from the class, lets interact with the observable itself. """ if instance is None: return self return self._get_value(instance) def __set__(self, instance: Owner, new_value: Value) -> None: """ Iff value has changed, __set__ processes the value, validates it, updates itself and notifies all change observers. """ old_value = self._get_value(instance) new_value = self._preprocess(new_value) if new_value is not old_value: # logic fires only in the case when the value changes self._validate(instance, new_value) # the new value is assumed to be valid self._set_value(instance, new_value) # notify observers about the change done self._notify(instance, old_value, new_value) def __delete__(self, instance: Owner) -> None: """ Sets default (undefined_value by default) as the value of the observable. NB: if the observable hasn't been set at all, there's no value, there is only a `self.default` attribute. """ self._set_value(instance, self.default) def _preprocess(self, value: Value) -> Value: """ Prepares assigned value BEFORE value is checked whether it is to be changed. Useful if your assigning process has to change the value in some way, ie. instantiates the class of the value or casts the value. Params: value -- raw value to reprocess. Returns: Preprocessed value. """ if not self.preprocessor: return value return self.preprocessor(value) def _validate(self, instance: Owner, new_value: Value = None) -> None: """ Fires validator using instance, old_value and new_value as arguments. The `validate` method may be called with a to-be-assigned value as the `new_value` in purpose of validating it pre-assignment; or without a new_value which means that the current value is validated. Params: instance -- instance of the owner new_value -- a value which is supposed to be set on the observable; the default value is the current value of the observable Raises: errors that are used by the validator """ if not self.validator: return old_value = self._get_value(instance) new_value = new_value or old_value self.validator(instance, old_value, new_value) def _notify(self, instance: Owner, old_value: Value, new_value: Value) -> None: """ Fires notifications to per-class and per-instance observers. Old value is passed as an argument, new value is just the current value (we are at the point right after the assignment). Params: instance -- instance of the owner. old_value -- value before assignment. new_value -- current value of the observable. """ # per-instance observers key = self._observer_key_pattern % self._label for observer in instance.__dict__.get(key, ()): observer(instance, old_value, new_value) # per-class observers for observer in self._class_observers or (): observer(instance, old_value, new_value) def add_class_observer(self, observer: Observer) -> None: """ Adds a function or method as a change observer on per-class basis. Params: observer -- a function or method of (Observable, old_value, new_value) -> None signature that is going to be called whenever the observable changes its value. It is supposed to serve as a observer of the observable's value. """ # TODO assert observer signature is valid self._class_observers.add(observer) def class_observer(self, observer: Observer) -> Observer: """ Decorator that marks a function or method as a change observer on per-class basis. Params: observer -- a function or method of (observable, old_value, new_value) -> None signature that is going to be called whenever the observable changes its value. It is supposed to serve as a observer of the observable's value. Returns: the initial `observer` argument untouched. It just adds it to the internal collection of observers of the Observable. Example usage: class MyObject: some_observable = Observable() @some_observable.class_observer def some_observable_activated(self, old_value, new_value, observable): do_sth_here """ # TODO assert observer signature is valid self.add_class_observer(observer) # it's intended as a decorator, so return the `observer` untouched return observer def add_instance_observer(self, instance: Owner, observer: Observer) -> None: """ Adds a function or method as a change observer on per-instance basis. Params: instance - the owner instance that the observer is connected to observer - a function or method of (owner_instance, old_value, new_value) -> None signature that is going to be called whenever the observable changes its value. It is supposed to serve as a observer of the observable's value. """ # we're asserting valid observer signature (without relying on # duck-typing), because observer is passed here, but the potential # TypeError is going to be raised much further during the runtime # TODO assert observer signature is valid assert is_argspec_valid(observer, arg_number=3) # per-instance change observers # observable signs itself with its label in the instance __dict__ key = self._observer_key_pattern % self._label instance.__dict__.setdefault(key, OrderedSet()).add(observer)
def test_empty_pickle(): empty_oset = OrderedSet() empty_roundtrip = pickle.loads(pickle.dumps(empty_oset)) assert empty_roundtrip == empty_oset
class Observable: _owner: Owner _label: str # pattern for __dict__ key on the owner class; space char is intended to be # sure we are not colliding with any proper attribute name _observer_key_pattern = '%s instance observers' def __init__( self, default=undefined_value, preprocessor: Preprocessor = None, validator: Validator = None, class_observers: t.Iterable[Observer] = None ): """ Params: default -- default value of the Observable. It is not validated (you are supposed to know what you are doing). Default: sentinel object "pca.data.observable.undefined_value" preprocessor -- a callable of signature: (raw_value) -> value which prepares what's going-to-be new value before the assignment and the validity checks. It's usable when you want to cast the value, instantiate a class to be a value or something similar. validator -- a callable of signature: (old_value) -> None which checks if the value is valid, whatever that means to the observable. class_observers -- iterable of per-class observers. With this argument you can declare the observers during Observable declaration. """ self.default = default self.preprocessor = preprocessor self.validator = validator self._class_observers = OrderedSet(class_observers) if class_observers else OrderedSet() def __set_name__(self, owner, name): self._owner = owner self._label = name @property def label(self) -> str: """Python name of the attribute under which the observable is hung on the owner class.""" return self._label def _get_value(self, instance: Owner) -> Value: """Technical detail of retrieving the value from the instance `__dict__`.""" return instance.__dict__.get(self._label, self.default) def _set_value(self, instance: Owner, value: Value): """Technical detail of setting the value at the instance `__dict__`.""" instance.__dict__[self._label] = value def __get__(self, instance: Owner, owner: t.Type) -> Value: """ If accessed from the instance, gets the value of the observable from the owner's `__dict__`. If accessed from the class, lets interact with the observable itself. """ if instance is None: return self return self._get_value(instance) def __set__(self, instance: Owner, new_value: Value) -> None: """ Iff value has changed, __set__ processes the value, validates it, updates itself and notifies all change observers. """ old_value = self._get_value(instance) new_value = self._preprocess(new_value) if new_value is not old_value: # logic fires only in the case when the value changes self._validate(instance, new_value) # the new value is assumed to be valid self._set_value(instance, new_value) # notify observers about the change done self._notify(instance, old_value, new_value) def __delete__(self, instance: Owner) -> None: """ Sets default (undefined_value by default) as the value of the observable. NB: if the observable hasn't been set at all, there's no value, there is only a `self.default` attribute. """ self._set_value(instance, self.default) def _preprocess(self, value: Value) -> Value: """ Prepares assigned value BEFORE value is checked whether it is to be changed. Useful if your assigning process has to change the value in some way, ie. instantiates the class of the value or casts the value. Params: value -- raw value to reprocess. Returns: Preprocessed value. """ if not self.preprocessor: return value return self.preprocessor(value) def _validate(self, instance: Owner, new_value: Value = None) -> None: """ Fires validator using instance, old_value and new_value as arguments. The `validate` method may be called with a to-be-assigned value as the `new_value` in purpose of validating it pre-assignment; or without a new_value which means that the current value is validated. Params: instance -- instance of the owner new_value -- a value which is supposed to be set on the observable; the default value is the current value of the observable Raises: errors that are used by the validator """ if not self.validator: return old_value = self._get_value(instance) new_value = new_value or old_value self.validator(instance, old_value, new_value) def _notify(self, instance: Owner, old_value: Value, new_value: Value) -> None: """ Fires notifications to per-class and per-instance observers. Old value is passed as an argument, new value is just the current value (we are at the point right after the assignment). Params: instance -- instance of the owner. old_value -- value before assignment. new_value -- current value of the observable. """ # per-instance observers key = self._observer_key_pattern % self._label for observer in instance.__dict__.get(key, ()): observer(instance, old_value, new_value) # per-class observers for observer in self._class_observers or (): observer(instance, old_value, new_value) def add_class_observer(self, observer: Observer) -> None: """ Adds a function or method as a change observer on per-class basis. Params: observer -- a function or method of (Observable, old_value, new_value) -> None signature that is going to be called whenever the observable changes its value. It is supposed to serve as a observer of the observable's value. """ # TODO assert observer signature is valid self._class_observers.add(observer) def class_observer(self, observer: Observer) -> Observer: """ Decorator that marks a function or method as a change observer on per-class basis. Params: observer -- a function or method of (observable, old_value, new_value) -> None signature that is going to be called whenever the observable changes its value. It is supposed to serve as a observer of the observable's value. Returns: the initial `observer` argument untouched. It just adds it to the internal collection of observers of the Observable. Example usage: class MyObject: some_observable = Observable() @some_observable.class_observer def some_observable_activated(self, old_value, new_value, observable): do_sth_here """ # TODO assert observer signature is valid self.add_class_observer(observer) # it's intended as a decorator, so return the `observer` untouched return observer def add_instance_observer(self, instance: Owner, observer: Observer) -> None: """ Adds a function or method as a change observer on per-instance basis. Params: instance - the owner instance that the observer is connected to observer - a function or method of (owner_instance, old_value, new_value) -> None signature that is going to be called whenever the observable changes its value. It is supposed to serve as a observer of the observable's value. """ # we're asserting valid observer signature (without relying on # duck-typing), because observer is passed here, but the potential # TypeError is going to be raised much further during the runtime # TODO assert observer signature is valid assert is_argspec_valid(observer, arg_number=3) # per-instance change observers # observable signs itself with its label in the instance __dict__ key = self._observer_key_pattern % self._label instance.__dict__.setdefault(key, OrderedSet()).add(observer)
def test_clear(): set1 = OrderedSet('abracadabra') set1.clear() assert len(set1) == 0 assert set1 == OrderedSet()
def test_remove_error(): # If we .remove() an element that's not there, we get a KeyError set1 = OrderedSet('abracadabra') with pytest.raises(KeyError): set1.remove('z')
def test_pickle(): set1 = OrderedSet('abracadabra') roundtrip = pickle.loads(pickle.dumps(set1)) assert roundtrip == set1
def test_tuples(): set1 = OrderedSet() tup = ('tuple', 1) set1.add(tup) assert set1.index(tup) == 0 assert set1[0] == tup
def test_order(): set1 = OrderedSet('abracadabra') assert len(set1) == 5 assert set1 == OrderedSet(['a', 'b', 'r', 'c', 'd']) assert list(reversed(set1)) == ['d', 'c', 'r', 'b', 'a']
def test_order(): set1 = OrderedSet("abracadabra") assert len(set1) == 5 assert set1 == OrderedSet(["a", "b", "r", "c", "d"]) assert list(reversed(set1)) == ["d", "c", "r", "b", "a"]