Skip to main content

A library which provides observable values to which other values can be bound

Project description

spellbind

Python 3.10+ Tests

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

git clone https://github.com/FancyNeuron/spellbind.git
cd spellbind
pip install -e .

Quick Start

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.

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:

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:

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:

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:

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

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:

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:

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:

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:

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

pytest

Type Checking

mypy src

Linting

flake8 .

Author: Georg Plaz

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

spellbind-0.2.3.tar.gz (30.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

spellbind-0.2.3-py3-none-any.whl (15.6 kB view details)

Uploaded Python 3

File details

Details for the file spellbind-0.2.3.tar.gz.

File metadata

  • Download URL: spellbind-0.2.3.tar.gz
  • Upload date:
  • Size: 30.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for spellbind-0.2.3.tar.gz
Algorithm Hash digest
SHA256 6120ca4b253b251137da4990bcf1601b60fc161dda14f0e85df56d07ee53dc1a
MD5 f386d91e99602ac1d89e957df10b2be2
BLAKE2b-256 2faf514438ae65ae7728e75097c483bf72114479ba2963ca31e23932c4717c8c

See more details on using hashes here.

Provenance

The following attestation bundles were made for spellbind-0.2.3.tar.gz:

Publisher: python-package.yml on FancyNeuron/spellbind

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file spellbind-0.2.3-py3-none-any.whl.

File metadata

  • Download URL: spellbind-0.2.3-py3-none-any.whl
  • Upload date:
  • Size: 15.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for spellbind-0.2.3-py3-none-any.whl
Algorithm Hash digest
SHA256 f53ab3a754873cb44534e4ed17c1d58bd27af6795babb081dba7bc338084172d
MD5 1bdc925d725aab38b419e411462664fd
BLAKE2b-256 3d12562f352d40f062eda3aa17f5d6867f2eeb868328927e69f0082141f59cf5

See more details on using hashes here.

Provenance

The following attestation bundles were made for spellbind-0.2.3-py3-none-any.whl:

Publisher: python-package.yml on FancyNeuron/spellbind

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page