Observable managed attributes (also known as "observable properties")
In summary:
- Declare observable properties using the
@observabledecorator instead of@property. - Subscribe/unsubscribe any number of callback functions to observable properties.
- If the value of an observable property changes, all subscribed callbacks are executed.
How to use
Declare observable properties in your class
Use the @observable decorator as you do with @property:
from observable_properties import observable
class Test():
def __init__(self):
self._value = 0
@observable
def value(self):
return self._value
@value.setter
def value(self, value):
self._value = value
Needless to say, observable properties are also regular properties and they work just the same:
t = Test()
t.value = 1000
print(f"Current value of t: {t.value}")
Subscribe to property changes
In the context of this library, an observer is a callback function to be executed when the value of a property is overwritten in a given object. For example:
def observer(instance,property_name,new_value):
print(f"{instance.__class__.__name__}.{property_name} = {new_value}")
To start observing a property, call subscribe() passing the instance and property name to observe along with the callback function:
from observable_properties import subscribe
subscribe(observer,t,"value")
Which means "subscribe observer to t.value".
Now, the observer is executed after the value of the observed property changes at the observed object:
t.value = 2000
prints:
Test.value = 2000
Additionally:
- Observers are not allowed to change the value of the observed property.
ObservablePropertyErroris raised in such a case. - The same observer can subscribe to many objects and properties.
- Any number of observers can subscribe to the same object and property. All of them will run. The execution order depends on subscription order.
Unsubscribe
To stop observing a property, call unsubscribe() with the same parameters that were given to subscribe():
from observable_properties import unsubscribe
unsubscribe(observer,t,"value")
Syntactic sugar
To speed things up, derive your class from the Observable class (note the capital letter).
The declaration itself does not differ too much from the previous example:
from observable_properties import Observable
class ObservableTest(Observable):
def __init__(self):
self._value = 0
@observable
def value(self):
return self._value
@value.setter
def value(self, value):
self._value = value
Observable classes offer subscribe() and unsubscribe() methods with a slightly different
syntax to their function counterparts:
ot = ObservableTest()
ot.subscribe("value",observer)
ot.value = 900
ot.unsubscribe("value", observer)
ot.value = 500
prints:
ObservableTest.value = 900
Observable changes triggered from non-setter methods
A public setter is not mandatory for observable properties of an Observable instance.
This is a common use case for computed properties.
Any method can notify changes in observable properties in two ways:
- Compute the new value inside the context returned by
self._observable(). The new value is automatically notified to all subscribers on exit. As an example, seeProduct.set()below. - Explicitly call
self._observable_notify()after the computation is finished. As an example, seeProduct.set_alternate()below.
class Product(Observable):
def __init__(self):
self._a = 0
self._b = 0
@observable
def value(self):
return self._a * self._b
def set(self, a, b):
with self._observable("value"):
self._a = a
self._b = b
def set_alternate(self, a, b):
changed = (a!=self._a) or (b!=self._b)
self._a = a
self._b = b
if (changed):
self._observable_notify("value")
mult = Product()
mult.subscribe("value", observer)
mult.set(3, 5)
mult.set_alternate(4,3)
prints:
Product.value = 15
Product.value = 12
Other notes
subscribe(),unsubscribe(),_observable()and_observable_notify()raiseObservablePropertyErroron non-observable or non-existing properties.- Subscribing twice to the same object and property has no effect.
- Unsubscribing to non-subscribed properties has no effect, but
unsubscribe()returns False. - Objects hold strong references to observers. Deleting an observer does not prevent it from running. Unsubscribe first.
- Coroutines are accepted as observers. They are executed by the means of asyncio.run()
Metadata
Release files for observable-properties 4.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| observable-properties-4.0.0.tar.gz | 16.9 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| observable_properties-4.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 31.9 kB
Release files / observable-properties-4.0.0.tar.gz
| Download URL | observable-properties-4.0.0.tar.gz |
|---|---|
| Size | 16.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
cf3f318ae07df428e6f496ec204f6257d5b28ed3097cd4463b6d2b91038d192d
|
|
BLAKE2b-256 checksum How to use checksums |
8bfaea9ddde11de55ffd41947da61fa27eb9c53b858c92cdf5b6c0d78a7bbb3f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/4.0.2 CPython/3.11.7
|
Release files / observable_properties-4.0.0-py3-none-any.whl
| Download URL | observable_properties-4.0.0-py3-none-any.whl |
|---|---|
| Size | 15.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
51664235f5bf9692ddafb4ef85cbef1f41de541f69105d6cb253f3a81468ab44
|
|
BLAKE2b-256 checksum How to use checksums |
4ceca7d2fc07ab94d2c57ea31de13d71e3368c6b3005298f8ff2675c19629608
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/4.0.2 CPython/3.11.7
|
Security for observable-properties 4.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.