spellbind


Namespellbind JSON
Version 0.4.0 PyPI version JSON
download
home_pageNone
SummaryA library which provides observable values to which other values can be bound
upload_time2025-07-19 01:08:59
maintainerNone
docs_urlNone
authorNone
requires_python>=3.10
licenseMIT License Copyright (c) 2025 Georg Plaz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
keywords reactive programming computed derived values variables data-binding observable events event-driven
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # spellbind

[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
[![Tests](https://github.com/FancyNeuron/spellbind/actions/workflows/python-package.yml/badge.svg)](https://github.com/FancyNeuron/spellbind/actions/workflows/python-package.yml)

> Reactive programming for Python with reactive variables and events.

spellbind is a reactive programming library that lets you create Variables that automatically update when their dependencies change, plus an event system for notifying observers.

## Installation

```bash
pip install spellbind
```

## Quick Start

```python
from spellbind.int_values import IntVariable
from spellbind.str_values import StrVariable

# Create reactive variables
name = StrVariable("Alice")
age = IntVariable(25)

# Create computed values that automatically update
greeting = name + " is " + age.to_str() + " years old"

print(greeting)  # "Alice is 25 years old"

# Update source values - computed values update automatically!
name.value = "Bob"
age.value = 30

print(greeting)  # "Bob is 30 years old"
```

## Core Concepts

### Values, Variables and Events

The foundation of spellbind consists of three key components:

**Values** are read-only reactive data that can be observed for changes. **Variables** are mutable Values that can be changed and bound to other Values. **Events** provide a way to notify observers when something happens.

```python
from spellbind.values import Constant
from spellbind.int_values import IntVariable
from spellbind.event import Event

# Variables can be changed
counter = IntVariable(0)
counter.value = 10

# Constants cannot be changed
pi = Constant(3.14159)

# Events notify observers
button_clicked = Event()
button_clicked.observe(lambda: print("Clicked!"))
button_clicked()  # Prints: "Clicked!"
```

### Reactive Bindings

Variables can be **bound** to other Values, making them automatically update:

```python
from spellbind.int_values import IntVariable

# Create computed values
base = IntVariable(10)
multiplier = IntVariable(3)
result = base * multiplier

# Bind variables to computed values
my_variable = IntVariable(0)
my_variable.bind_to(result)

print(my_variable)  # 30

# Updates propagate automatically
base.value = 20
print(my_variable)  # 60

# Unbind to break connections
my_variable.unbind()
```

## Advanced Features

### Weak vs Strong Binding

Control memory management with binding strength:

```python
from spellbind.str_values import StrVariable

source = StrVariable("hello")
target = StrVariable("")

# Strong binding (default) - keeps source alive
target.bind_to(source, bind_weakly=False)

# Weak binding - allows source to be garbage collected
target.bind_to(source, bind_weakly=True)
```

### Circular Dependency Detection

spellbind automatically prevents circular dependencies:

```python
from spellbind.int_values import IntVariable

a = IntVariable(1)
b = IntVariable(2)

a.bind_to(b)
# b.bind_to(a)  # This would raise RecursionError
```

### Observing Changes

React to value changes with observers:

```python
from spellbind.int_values import IntVariable


def on_value_change(new_value):
    print(f"Value changed to: {new_value}")


my_var = IntVariable(42)
my_var.observe(on_value_change)

my_var.value = 100  # Prints: "Value changed to: 100"
```

## Event System

spellbind includes an event system for notifying observers when things happen.

### Basic Events

```python
from spellbind.event import Event

# Create an event
button_clicked = Event()


# Add observers
def handle_click():
    print("Button was clicked!")


button_clicked.observe(handle_click)

# Trigger the event
button_clicked()  # Prints: "Button was clicked!"
```

### Value Events

Events that pass data to observers:

```python
from spellbind.event import ValueEvent

user_logged_in = ValueEvent[str]()


def welcome_user(username: str):
    print(f"Welcome, {username}!")


user_logged_in.observe(welcome_user)
user_logged_in("Alice")  # Prints: "Welcome, Alice!"
```

### Multi-Parameter Events

Events with multiple parameters:

```python
from spellbind.event import BiEvent, TriEvent

# Two parameters
position_changed = BiEvent[int, int]()
position_changed.observe(lambda x, y: print(f"Position: ({x}, {y})"))
position_changed(10, 20)  # Prints: "Position: (10, 20)"

# Three parameters
rgb_changed = TriEvent[int, int, int]()
rgb_changed.observe(lambda r, g, b: print(f"Color: rgb({r}, {g}, {b})"))
rgb_changed(255, 128, 0)  # Prints: "Color: rgb(255, 128, 0)"
```

### Weak Observation

Prevent memory leaks with weak observers:

```python
from spellbind.event import Event

event = Event()


def temporary_handler():
    print("Handling event")


# Weak observation - handler can be garbage collected
event.weak_observe(temporary_handler)
```

## Example Application

Here's a practical example showing how to create automatically positioned windows:

```python
from spellbind.int_values import IntVariable


class Window:
    def __init__(self, x: int, y: int, width: int, height: int):
        self.x = IntVariable(x)
        self.y = IntVariable(y)
        self.width = IntVariable(width)
        self.height = IntVariable(height)

    def __repr__(self):
        return f"Window(x={self.x.value}, y={self.y.value}, width={self.width.value}, height={self.height.value})"


# Create two windows
main_window = Window(100, 50, 800, 600)
sidebar_window = Window(0, 0, 200, 400)

# Automatically position sidebar to the right of main window
margin = IntVariable(10)
sidebar_window.x.bind_to(main_window.x + main_window.width + margin)
sidebar_window.y.bind_to(main_window.y)

print(main_window)  # Window(x=100, y=50, width=800, height=600)
print(sidebar_window)  # Window(x=910, y=50, width=200, height=400)

# Moving the main window automatically repositions the sidebar
main_window.x.value = 200
main_window.y.value = 100

print(main_window)  # Window(x=200, y=100, width=800, height=600)
print(sidebar_window)  # Window(x=1010, y=100, width=200, height=400)

# Changing margin updates sidebar position
margin.value = 20
print(sidebar_window)  # Window(x=1020, y=100, width=200, height=400)
```

## API Reference

### Core Classes

- **`Value[T]`** - Type for all reactive values, useful for typing function parameters
- **`Variable[T]`** - Type for mutable values, useful for typing function parameters  
- **`Constant[T]`** - Immutable value

### Type-Specific Classes

- **`IntValue`**, **`IntVariable`** - Integer values with arithmetic operations
- **`FloatValue`**, **`FloatVariable`** - Float values with arithmetic operations  
- **`StrValue`**, **`StrVariable`** - String values with concatenation
- **`BoolValue`** - Boolean values with logical operations

### Event Classes

- **`Event`** - Basic event with no parameters
- **`ValueEvent[T]`** - Event that passes one value
- **`BiEvent[S, T]`** - Event that passes two values
- **`TriEvent[S, T, U]`** - Event that passes three values

## Development

### Running Tests

```bash
pytest
```

### Type Checking

```bash
mypy src
```

### Linting

```bash
flake8 .
```

---

Author: Georg Plaz

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "spellbind",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.10",
    "maintainer_email": null,
    "keywords": "reactive, programming, computed, derived, values, variables, data-binding, observable, events, event-driven",
    "author": null,
    "author_email": "Georg Plaz <georg.plaz@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/57/7d/ae609794174bd70500ccef59b099af7ef72a9480178d6992401cff1f590c/spellbind-0.4.0.tar.gz",
    "platform": null,
    "description": "# spellbind\n\n[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)\n[![Tests](https://github.com/FancyNeuron/spellbind/actions/workflows/python-package.yml/badge.svg)](https://github.com/FancyNeuron/spellbind/actions/workflows/python-package.yml)\n\n> Reactive programming for Python with reactive variables and events.\n\nspellbind is a reactive programming library that lets you create Variables that automatically update when their dependencies change, plus an event system for notifying observers.\n\n## Installation\n\n```bash\npip install spellbind\n```\n\n## Quick Start\n\n```python\nfrom spellbind.int_values import IntVariable\nfrom spellbind.str_values import StrVariable\n\n# Create reactive variables\nname = StrVariable(\"Alice\")\nage = IntVariable(25)\n\n# Create computed values that automatically update\ngreeting = name + \" is \" + age.to_str() + \" years old\"\n\nprint(greeting)  # \"Alice is 25 years old\"\n\n# Update source values - computed values update automatically!\nname.value = \"Bob\"\nage.value = 30\n\nprint(greeting)  # \"Bob is 30 years old\"\n```\n\n## Core Concepts\n\n### Values, Variables and Events\n\nThe foundation of spellbind consists of three key components:\n\n**Values** are read-only reactive data that can be observed for changes. **Variables** are mutable Values that can be changed and bound to other Values. **Events** provide a way to notify observers when something happens.\n\n```python\nfrom spellbind.values import Constant\nfrom spellbind.int_values import IntVariable\nfrom spellbind.event import Event\n\n# Variables can be changed\ncounter = IntVariable(0)\ncounter.value = 10\n\n# Constants cannot be changed\npi = Constant(3.14159)\n\n# Events notify observers\nbutton_clicked = Event()\nbutton_clicked.observe(lambda: print(\"Clicked!\"))\nbutton_clicked()  # Prints: \"Clicked!\"\n```\n\n### Reactive Bindings\n\nVariables can be **bound** to other Values, making them automatically update:\n\n```python\nfrom spellbind.int_values import IntVariable\n\n# Create computed values\nbase = IntVariable(10)\nmultiplier = IntVariable(3)\nresult = base * multiplier\n\n# Bind variables to computed values\nmy_variable = IntVariable(0)\nmy_variable.bind_to(result)\n\nprint(my_variable)  # 30\n\n# Updates propagate automatically\nbase.value = 20\nprint(my_variable)  # 60\n\n# Unbind to break connections\nmy_variable.unbind()\n```\n\n## Advanced Features\n\n### Weak vs Strong Binding\n\nControl memory management with binding strength:\n\n```python\nfrom spellbind.str_values import StrVariable\n\nsource = StrVariable(\"hello\")\ntarget = StrVariable(\"\")\n\n# Strong binding (default) - keeps source alive\ntarget.bind_to(source, bind_weakly=False)\n\n# Weak binding - allows source to be garbage collected\ntarget.bind_to(source, bind_weakly=True)\n```\n\n### Circular Dependency Detection\n\nspellbind automatically prevents circular dependencies:\n\n```python\nfrom spellbind.int_values import IntVariable\n\na = IntVariable(1)\nb = IntVariable(2)\n\na.bind_to(b)\n# b.bind_to(a)  # This would raise RecursionError\n```\n\n### Observing Changes\n\nReact to value changes with observers:\n\n```python\nfrom spellbind.int_values import IntVariable\n\n\ndef on_value_change(new_value):\n    print(f\"Value changed to: {new_value}\")\n\n\nmy_var = IntVariable(42)\nmy_var.observe(on_value_change)\n\nmy_var.value = 100  # Prints: \"Value changed to: 100\"\n```\n\n## Event System\n\nspellbind includes an event system for notifying observers when things happen.\n\n### Basic Events\n\n```python\nfrom spellbind.event import Event\n\n# Create an event\nbutton_clicked = Event()\n\n\n# Add observers\ndef handle_click():\n    print(\"Button was clicked!\")\n\n\nbutton_clicked.observe(handle_click)\n\n# Trigger the event\nbutton_clicked()  # Prints: \"Button was clicked!\"\n```\n\n### Value Events\n\nEvents that pass data to observers:\n\n```python\nfrom spellbind.event import ValueEvent\n\nuser_logged_in = ValueEvent[str]()\n\n\ndef welcome_user(username: str):\n    print(f\"Welcome, {username}!\")\n\n\nuser_logged_in.observe(welcome_user)\nuser_logged_in(\"Alice\")  # Prints: \"Welcome, Alice!\"\n```\n\n### Multi-Parameter Events\n\nEvents with multiple parameters:\n\n```python\nfrom spellbind.event import BiEvent, TriEvent\n\n# Two parameters\nposition_changed = BiEvent[int, int]()\nposition_changed.observe(lambda x, y: print(f\"Position: ({x}, {y})\"))\nposition_changed(10, 20)  # Prints: \"Position: (10, 20)\"\n\n# Three parameters\nrgb_changed = TriEvent[int, int, int]()\nrgb_changed.observe(lambda r, g, b: print(f\"Color: rgb({r}, {g}, {b})\"))\nrgb_changed(255, 128, 0)  # Prints: \"Color: rgb(255, 128, 0)\"\n```\n\n### Weak Observation\n\nPrevent memory leaks with weak observers:\n\n```python\nfrom spellbind.event import Event\n\nevent = Event()\n\n\ndef temporary_handler():\n    print(\"Handling event\")\n\n\n# Weak observation - handler can be garbage collected\nevent.weak_observe(temporary_handler)\n```\n\n## Example Application\n\nHere's a practical example showing how to create automatically positioned windows:\n\n```python\nfrom spellbind.int_values import IntVariable\n\n\nclass Window:\n    def __init__(self, x: int, y: int, width: int, height: int):\n        self.x = IntVariable(x)\n        self.y = IntVariable(y)\n        self.width = IntVariable(width)\n        self.height = IntVariable(height)\n\n    def __repr__(self):\n        return f\"Window(x={self.x.value}, y={self.y.value}, width={self.width.value}, height={self.height.value})\"\n\n\n# Create two windows\nmain_window = Window(100, 50, 800, 600)\nsidebar_window = Window(0, 0, 200, 400)\n\n# Automatically position sidebar to the right of main window\nmargin = IntVariable(10)\nsidebar_window.x.bind_to(main_window.x + main_window.width + margin)\nsidebar_window.y.bind_to(main_window.y)\n\nprint(main_window)  # Window(x=100, y=50, width=800, height=600)\nprint(sidebar_window)  # Window(x=910, y=50, width=200, height=400)\n\n# Moving the main window automatically repositions the sidebar\nmain_window.x.value = 200\nmain_window.y.value = 100\n\nprint(main_window)  # Window(x=200, y=100, width=800, height=600)\nprint(sidebar_window)  # Window(x=1010, y=100, width=200, height=400)\n\n# Changing margin updates sidebar position\nmargin.value = 20\nprint(sidebar_window)  # Window(x=1020, y=100, width=200, height=400)\n```\n\n## API Reference\n\n### Core Classes\n\n- **`Value[T]`** - Type for all reactive values, useful for typing function parameters\n- **`Variable[T]`** - Type for mutable values, useful for typing function parameters  \n- **`Constant[T]`** - Immutable value\n\n### Type-Specific Classes\n\n- **`IntValue`**, **`IntVariable`** - Integer values with arithmetic operations\n- **`FloatValue`**, **`FloatVariable`** - Float values with arithmetic operations  \n- **`StrValue`**, **`StrVariable`** - String values with concatenation\n- **`BoolValue`** - Boolean values with logical operations\n\n### Event Classes\n\n- **`Event`** - Basic event with no parameters\n- **`ValueEvent[T]`** - Event that passes one value\n- **`BiEvent[S, T]`** - Event that passes two values\n- **`TriEvent[S, T, U]`** - Event that passes three values\n\n## Development\n\n### Running Tests\n\n```bash\npytest\n```\n\n### Type Checking\n\n```bash\nmypy src\n```\n\n### Linting\n\n```bash\nflake8 .\n```\n\n---\n\nAuthor: Georg Plaz\n",
    "bugtrack_url": null,
    "license": "MIT License\n        \n        Copyright (c) 2025 Georg Plaz\n        \n        Permission is hereby granted, free of charge, to any person obtaining a copy\n        of this software and associated documentation files (the \"Software\"), to deal\n        in the Software without restriction, including without limitation the rights\n        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n        copies of the Software, and to permit persons to whom the Software is\n        furnished to do so, subject to the following conditions:\n        \n        The above copyright notice and this permission notice shall be included in all\n        copies or substantial portions of the Software.\n        \n        THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n        SOFTWARE.",
    "summary": "A library which provides observable values to which other values can be bound",
    "version": "0.4.0",
    "project_urls": {
        "Documentation": "https://github.com/FancyNeuron/spellbind#readme",
        "Homepage": "https://github.com/FancyNeuron/spellbind",
        "Issues": "https://github.com/FancyNeuron/spellbind/issues",
        "Repository": "https://github.com/FancyNeuron/spellbind"
    },
    "split_keywords": [
        "reactive",
        " programming",
        " computed",
        " derived",
        " values",
        " variables",
        " data-binding",
        " observable",
        " events",
        " event-driven"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "c422bc6be416582fe34c3ceb06117462091b003dd852d99344ed39e9a656ded8",
                "md5": "f1c9451bcf877d2b2d9375e3aee11317",
                "sha256": "2ab5aa639da07bacdf404b2b0a85daaad465dba51a61d8936993feba04e2205c"
            },
            "downloads": -1,
            "filename": "spellbind-0.4.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "f1c9451bcf877d2b2d9375e3aee11317",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.10",
            "size": 32812,
            "upload_time": "2025-07-19T01:08:58",
            "upload_time_iso_8601": "2025-07-19T01:08:58.736552Z",
            "url": "https://files.pythonhosted.org/packages/c4/22/bc6be416582fe34c3ceb06117462091b003dd852d99344ed39e9a656ded8/spellbind-0.4.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "577dae609794174bd70500ccef59b099af7ef72a9480178d6992401cff1f590c",
                "md5": "203e966df89b509360c70d5603e98c95",
                "sha256": "2a5577d7068d71f8fb2167362489a39d73af89f2adbd8c5ce7d1161fb7122a08"
            },
            "downloads": -1,
            "filename": "spellbind-0.4.0.tar.gz",
            "has_sig": false,
            "md5_digest": "203e966df89b509360c70d5603e98c95",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.10",
            "size": 61822,
            "upload_time": "2025-07-19T01:08:59",
            "upload_time_iso_8601": "2025-07-19T01:08:59.816208Z",
            "url": "https://files.pythonhosted.org/packages/57/7d/ae609794174bd70500ccef59b099af7ef72a9480178d6992401cff1f590c/spellbind-0.4.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-07-19 01:08:59",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "FancyNeuron",
    "github_project": "spellbind#readme",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "spellbind"
}
        
Elapsed time: 1.49604s