# Binding
This package brings property binding to Python.
It allows you to bind attributes of one object to other attributes of itself or other objects like so:
```Python
label.text.bind(model.value)
```
That means, whenever `model.value` is changed, `label.text` changes as well.
# Installation
This package can be installed using the Python package installer [pip](https://pypi.org/project/pip/):
```bash
python3 -m pip install binding
```
Alternatively you can find the [source code](https://github.com/zauberzeug/binding) on GitHub.
# Usage
You can apply binding in two different ways: automatic updates using bindable properties or by calling an update functional explicitely.
Furthermore, you can specify the binding direction as well as converter functions.
The following examples give a more detailed explanation.
The code snippets build upon each other and are meant to be called in succession.
## Bindable properties
If you have control over the class implementation, you can introduce a `BindableProperty` for the respective attributes. It will intercept each write access the attribute and propagate the changed value to bound properties:
```python
from binding import BindableProperty
class Person:
name = BindableProperty()
def __init__(self, name=None):
self.name = name
class Car:
driver = BindableProperty()
def __init__(self, driver=None):
self.driver = driver
person = Person('Robert')
car = Car()
car.driver.bind(person.name)
assert car.driver == person.name == 'Robert'
person.name = 'Bob'
assert car.driver == person.name == 'Bob'
```
## Binding with non-bindable attributes
Suppose you have a class which you cannot or don't want to change.
That means it has no `BindableProperty` to observe value changes.
You can bind its attributes nevertheless:
```python
class License:
def __init__(self, name=None):
self.name = name
license = License()
license.name.bind(person.name)
person.name = 'Todd'
assert license.name == person.name == 'Todd'
```
But if the license name is changed, there is no `BindableProperty` to notice write access to its value.
We have to manually trigger the propagation to bound objects.
```python
from binding import update
license.name = 'Ben'
assert person.name != license.name == 'Ben'
update()
assert person.name == license.name == 'Ben'
```
## One-way binding
The `.bind()` method registers two-way binding.
But you can also specify one-way binding using `.bind_from()` or `.bind_to()`, respectively.
In the following example `car` receives updates `person`, but not the other way around.
```python
person = Person('Ken')
car = Car()
car.driver.bind_from(person.name)
assert car.driver == person.name == 'Ken'
person.name = 'Sam'
assert car.driver == person.name == 'Sam'
car.driver = 'Seth'
assert car.driver != person.name == 'Sam'
```
Likewise you can specify forward binding to let `person` be updated when `car` changes:
```python
person = Person('Keith')
car = Car()
car.driver.bind_to(person.name)
assert car.driver == person.name == None
car.driver = 'Kent'
assert car.driver == person.name == 'Kent'
person.name = 'Grant'
assert car.driver != person.name == 'Grant'
```
## Converters
For all types of binding - forward, backward, two-way, via bindable properties or non-bindable attributes - you can define converter functions that translate values from one side to another.
The following example demonstrates the conversion between Celsius and Fahrenheit.
```python
class Temperature:
c = BindableProperty()
f = BindableProperty()
def __init__(self):
self.c = 0.0
self.f = 0.0
t = Temperature()
t.f.bind(t.c, forward=lambda f: (f - 32) / 1.8, backward=lambda c: c * 1.8 + 32)
assert t.c == 0.0 and t.f == 32.0
t.f = 68.0
assert t.c == 20.0 and t.f == 68.0
t.c = 100.0
assert t.c == 100.0 and t.f == 212.0
```
Note that `bind_to()` only needs a `forward` converter.
Similarly `bind_from` has only a `backward` converter.
# Implementation and dependencies
To achieve such a lean API we utilize three main techniques:
- For extending basic types with `bind()`, `bind_to()` and `bind_from()` methods we use `curse` from the [forbiddenfruit](https://pypi.org/project/forbiddenfruit/) package.
- For intercepting write access to attributes we implement `BindableProperties` as [descriptors](https://docs.python.org/3/howto/descriptor.html).
- For finding the object and attribute name of the caller and the argument of our `bind()` methods we use inspection tools from the [inspect](https://docs.python.org/3/library/inspect.html) and [executing](https://pypi.org/project/executing/) packages.
Raw data
{
"_id": null,
"home_page": "https://github.com/zauberzeug/binding",
"name": "binding",
"maintainer": "",
"docs_url": null,
"requires_python": ">=3.7,<4.0",
"maintainer_email": "",
"keywords": "bind,properties,ui,automatic,values",
"author": "Zauberzeug GmbH",
"author_email": "info@zauberzeug.com",
"download_url": "https://files.pythonhosted.org/packages/b2/5c/7cc4fad8448e70aee0a5e6943658dbf8f086987d8f2dc260b87e1439243e/binding-0.3.1.tar.gz",
"platform": "",
"description": "# Binding\n\nThis package brings property binding to Python.\nIt allows you to bind attributes of one object to other attributes of itself or other objects like so:\n\n```Python\nlabel.text.bind(model.value)\n```\n\nThat means, whenever `model.value` is changed, `label.text` changes as well.\n\n# Installation\n\nThis package can be installed using the Python package installer [pip](https://pypi.org/project/pip/):\n\n```bash\npython3 -m pip install binding\n```\n\nAlternatively you can find the [source code](https://github.com/zauberzeug/binding) on GitHub.\n\n# Usage\n\nYou can apply binding in two different ways: automatic updates using bindable properties or by calling an update functional explicitely.\nFurthermore, you can specify the binding direction as well as converter functions.\n\nThe following examples give a more detailed explanation.\nThe code snippets build upon each other and are meant to be called in succession.\n\n## Bindable properties\n\nIf you have control over the class implementation, you can introduce a `BindableProperty` for the respective attributes. It will intercept each write access the attribute and propagate the changed value to bound properties:\n\n```python\nfrom binding import BindableProperty\n\nclass Person:\n\n name = BindableProperty()\n\n def __init__(self, name=None):\n\n self.name = name\n\nclass Car:\n\n driver = BindableProperty()\n\n def __init__(self, driver=None):\n\n self.driver = driver\n\nperson = Person('Robert')\ncar = Car()\ncar.driver.bind(person.name)\nassert car.driver == person.name == 'Robert'\n\nperson.name = 'Bob'\nassert car.driver == person.name == 'Bob'\n```\n\n## Binding with non-bindable attributes\n\nSuppose you have a class which you cannot or don't want to change.\nThat means it has no `BindableProperty` to observe value changes.\nYou can bind its attributes nevertheless:\n\n```python\nclass License:\n\n def __init__(self, name=None):\n\n self.name = name\n\nlicense = License()\nlicense.name.bind(person.name)\nperson.name = 'Todd'\nassert license.name == person.name == 'Todd'\n```\n\nBut if the license name is changed, there is no `BindableProperty` to notice write access to its value.\nWe have to manually trigger the propagation to bound objects.\n\n```python\nfrom binding import update\n\nlicense.name = 'Ben'\nassert person.name != license.name == 'Ben'\n\nupdate()\nassert person.name == license.name == 'Ben'\n```\n\n## One-way binding\n\nThe `.bind()` method registers two-way binding.\nBut you can also specify one-way binding using `.bind_from()` or `.bind_to()`, respectively.\nIn the following example `car` receives updates `person`, but not the other way around.\n\n```python\nperson = Person('Ken')\ncar = Car()\n\ncar.driver.bind_from(person.name)\nassert car.driver == person.name == 'Ken'\n\nperson.name = 'Sam'\nassert car.driver == person.name == 'Sam'\n\ncar.driver = 'Seth'\nassert car.driver != person.name == 'Sam'\n```\n\nLikewise you can specify forward binding to let `person` be updated when `car` changes:\n\n```python\nperson = Person('Keith')\ncar = Car()\n\ncar.driver.bind_to(person.name)\nassert car.driver == person.name == None\n\ncar.driver = 'Kent'\nassert car.driver == person.name == 'Kent'\n\nperson.name = 'Grant'\nassert car.driver != person.name == 'Grant'\n```\n\n## Converters\n\nFor all types of binding - forward, backward, two-way, via bindable properties or non-bindable attributes - you can define converter functions that translate values from one side to another.\nThe following example demonstrates the conversion between Celsius and Fahrenheit.\n\n```python\nclass Temperature:\n\n c = BindableProperty()\n f = BindableProperty()\n\n def __init__(self):\n\n self.c = 0.0\n self.f = 0.0\n\nt = Temperature()\nt.f.bind(t.c, forward=lambda f: (f - 32) / 1.8, backward=lambda c: c * 1.8 + 32)\nassert t.c == 0.0 and t.f == 32.0\n\nt.f = 68.0\nassert t.c == 20.0 and t.f == 68.0\n\nt.c = 100.0\nassert t.c == 100.0 and t.f == 212.0\n```\n\nNote that `bind_to()` only needs a `forward` converter.\nSimilarly `bind_from` has only a `backward` converter.\n\n# Implementation and dependencies\n\nTo achieve such a lean API we utilize three main techniques:\n\n- For extending basic types with `bind()`, `bind_to()` and `bind_from()` methods we use `curse` from the [forbiddenfruit](https://pypi.org/project/forbiddenfruit/) package.\n\n- For intercepting write access to attributes we implement `BindableProperties` as [descriptors](https://docs.python.org/3/howto/descriptor.html).\n\n- For finding the object and attribute name of the caller and the argument of our `bind()` methods we use inspection tools from the [inspect](https://docs.python.org/3/library/inspect.html) and [executing](https://pypi.org/project/executing/) packages.\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Bindable properties for Python",
"version": "0.3.1",
"project_urls": {
"Homepage": "https://github.com/zauberzeug/binding",
"Repository": "https://github.com/zauberzeug/binding"
},
"split_keywords": [
"bind",
"properties",
"ui",
"automatic",
"values"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "2dd09e4a53d68388da4088dc7b8ae7bfc0e4ba59a460d59cfef4fdd39351394d",
"md5": "e78795406c42cfa63ffd35268d55392d",
"sha256": "1bd36c3c1abb388fb15bd6967d340a7025e23b54b622ca9a6e43d4c1caf14b55"
},
"downloads": -1,
"filename": "binding-0.3.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "e78795406c42cfa63ffd35268d55392d",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.7,<4.0",
"size": 4885,
"upload_time": "2021-07-15T08:19:49",
"upload_time_iso_8601": "2021-07-15T08:19:49.984824Z",
"url": "https://files.pythonhosted.org/packages/2d/d0/9e4a53d68388da4088dc7b8ae7bfc0e4ba59a460d59cfef4fdd39351394d/binding-0.3.1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "b25c7cc4fad8448e70aee0a5e6943658dbf8f086987d8f2dc260b87e1439243e",
"md5": "22162008916d2bfd597854ca18532a27",
"sha256": "66bc5c465369ccc35c0d587865f1c9a9a8abc41cfa275f4adc748ae42dcc0d7f"
},
"downloads": -1,
"filename": "binding-0.3.1.tar.gz",
"has_sig": false,
"md5_digest": "22162008916d2bfd597854ca18532a27",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.7,<4.0",
"size": 5078,
"upload_time": "2021-07-15T08:19:51",
"upload_time_iso_8601": "2021-07-15T08:19:51.528212Z",
"url": "https://files.pythonhosted.org/packages/b2/5c/7cc4fad8448e70aee0a5e6943658dbf8f086987d8f2dc260b87e1439243e/binding-0.3.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2021-07-15 08:19:51",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "zauberzeug",
"github_project": "binding",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "binding"
}