Skip to content

Observant Integration

QtPie's reactive features are powered by Observant (PyPI), a reactive state management library for Python.

You don't need to understand Observant to use QtPie - the state() function and Widget[T] handle everything automatically. This page is for users who want to understand the internals or use Observant directly.


What is Observant?

Observant provides:

  • Observable - A value that notifies listeners when it changes
  • ObservableProxy - Wraps any object (dataclass, etc.) making all its fields observable
  • Validation - Field-level validation with reactive error lists
  • Dirty Tracking - Know which fields changed since last save
  • Undo/Redo - Per-field history with configurable limits

How QtPie Uses Observant

state() Fields

When you use state():

@widget
class Counter(QWidget):
    count: int = state(0)

Under the hood, QtPie creates an ObservableProxy that wraps your value. When you assign to self.count, the proxy notifies all bound widgets to update.

Widget[T] Models

When you use Widget[T]:

@widget
class PersonEditor(QWidget, Widget[Person]):
    name: QLineEdit = make(QLineEdit)

QtPie automatically:

  1. Creates a Person() instance as self.model
  2. Wraps it in ObservableProxy as self.model_observable_proxy
  3. Auto-binds widget fields to model fields by name

The model_observable_proxy enables two-way binding, validation, dirty tracking, and undo/redo.


Using Observant Directly

Sometimes you need direct access to Observant features.

Subscribing to Changes

from qtpie.state import get_state_observable

@widget
class Counter(QWidget):
    count: int = state(0)

    def setup(self) -> None:
        obs = get_state_observable(self, "count")
        obs.on_change(lambda value: print(f"Count changed to {value}"))

Working with Widget[T] Proxy

@widget
class PersonEditor(QWidget, Widget[Person]):
    name: QLineEdit = make(QLineEdit)

    def setup(self) -> None:
        # Get observable for a field
        name_obs = self.model_observable_proxy.observable(str, "name")

        # Subscribe to changes
        name_obs.on_change(lambda v: print(f"Name: {v}"))

        # Read/write
        current = name_obs.get()
        name_obs.set("Alice")

Nested Path Access

# Get observable for nested path
city_obs = self.model_observable_proxy.observable_for_path("address.city")

# With optional chaining
owner_name_obs = self.model_observable_proxy.observable_for_path("owner?.name")

When to Use Observant Directly

You usually don't need to. QtPie's bind= parameter handles most cases.

Use Observant directly when you need:

  • Custom change handlers beyond what bind= provides
  • Programmatic access to observables for complex logic
  • Features not exposed through QtPie's API

Learn More


See Also