Skip to main content

A library for creating reactive Pydantic models using RxPy.

Project description

Reactive Pydantic

A powerful library for creating reactive Pydantic models with observable field changes using RxPY. This library extends Pydantic models with reactive capabilities, allowing you to observe and react to field changes in real-time.

Features

  • 🔄 Reactive Models: Extend Pydantic models with reactive capabilities
  • 📡 Field Observation: Observe changes to specific fields across all instances
  • 🎯 Instance-Specific Observation: Watch changes on individual model instances
  • 🚀 Custom Reactive Operators: Built-in operators for filtering, debouncing, and buffering
  • Async Support: Full support for async observation patterns
  • 🔍 Event Filtering: Filter events by type, field, or model instance
  • 🧪 Type Safe: Full type annotation support with modern Pydantic v2
  • 🧪 Validation Events: React to validation success/failure events
  • 🎨 Computed Properties: Create reactive computed properties that update automatically

Installation

pip install reactive-pydantic

Or with Poetry:

poetry add reactive-pydantic

Quick Start

from reactive_pydantic import ReactiveModel, reactive_field
import reactivex.operators as ops

class User(ReactiveModel):
    name: str = reactive_field(default="")
    age: int = reactive_field(default=0)
    email: str = reactive_field(default="")

# Observe field changes across all User instances
User.observe_field("name").pipe(
    ops.distinct_until_changed(),
    ops.map(lambda event: event.new_value)
).subscribe(lambda value: print(f"Name changed to: {value}"))

# Create and modify users
user = User(name="Alice", age=25)
user.name = "Alice Smith"  # Prints: Name changed to: Alice Smith

Core Concepts

Reactive Models

Reactive models are Pydantic models that emit events when their fields change:

from reactive_pydantic import ReactiveModel, reactive_field

class Product(ReactiveModel):
    name: str = reactive_field(default="")
    price: float = reactive_field(default=0.0)
    in_stock: bool = reactive_field(default=True)

Field Observation

Observe changes to specific fields across all instances of a model:

# Observe all name changes
Product.observe_field("name").subscribe(
    lambda event: print(f"Product name changed: {event.old_value} -> {event.new_value}")
)

# Observe price changes with custom operators
from reactive_pydantic.operators import map_to_value

Product.observe_field("price").pipe(
    map_to_value()  # Extract just the new value
).subscribe(lambda price: print(f"New price: ${price}"))

Instance-Specific Observation

Watch changes on individual model instances:

product = Product(name="Widget")

# Observe all changes to this specific instance
product.observe_instance().subscribe(
    lambda event: print(f"Field {event.field_name} changed on this product")
)

product.name = "Super Widget"  # Triggers the observer

Custom Operators

The library includes several custom operators for common reactive patterns:

Field Filtering

from reactive_pydantic.operators import where_field, map_to_value

# Only observe 'email' field changes
User.observe_model().pipe(
    where_field("email"),
    map_to_value()
).subscribe(lambda email: print(f"Email updated: {email}"))

Debouncing

from reactive_pydantic.operators import debounce_changes

# Debounce rapid changes (wait 500ms after last change)
User.observe_field("name").pipe(
    debounce_changes(0.5),
    map_to_value()
).subscribe(lambda name: print(f"Name settled on: {name}"))

Buffering

from reactive_pydantic.operators import buffer_changes

# Buffer every 3 changes
User.observe_field("age").pipe(
    buffer_changes(3)
).subscribe(lambda events: print(f"Received {len(events)} age changes"))

Event Type Filtering

from reactive_pydantic.operators import where_event_type
from reactive_pydantic.events import EventType

# Only observe field change events (not validation events)
User.observe_model().pipe(
    where_event_type(EventType.FIELD_CHANGED)
).subscribe(lambda event: print(f"Field changed: {event.field_name}"))

Advanced Usage

Multiple Model Types

import reactivex as rx

# Combine observables from different models
user_changes = User.observe_field("name")
product_changes = Product.observe_field("price")

# Merge streams
rx.merge(user_changes, product_changes).subscribe(
    lambda event: print(f"Something changed: {event}")
)

Async Observation

import asyncio

async def async_handler(event):
    await asyncio.sleep(0.1)  # Simulate async work
    print(f"Processed change: {event.field_name}")

User.observe_field("email").subscribe(async_handler)

Complex Filtering

import reactivex.operators as ops

# Complex filtering example
User.observe_model().pipe(
    ops.filter(lambda event: event.field_name in ["name", "email"]),
    ops.filter(lambda event: len(str(event.new_value)) > 3),
    ops.map(lambda event: f"{event.field_name}: {event.new_value}")
).subscribe(print)

Event Types

The library defines several event types:

  • EventType.FIELD_CHANGED: Emitted when a field value changes
  • EventType.MODEL_CREATED: Emitted when a model instance is created
  • EventType.VALIDATION_SUCCESS: Emitted when validation succeeds
  • EventType.VALIDATION_ERROR: Emitted when validation fails

API Reference

ReactiveModel

Base class for reactive Pydantic models.

Class Methods

  • observe_field(field_name: str) -> Observable[FieldChangeEvent]: Observe changes to a specific field across all instances
  • observe_model() -> Observable[BaseEvent]: Observe all events from all instances of this model

Instance Methods

  • observe_instance() -> Observable[BaseEvent]: Observe all events from this specific instance

reactive_field

Function to create reactive fields.

def reactive_field(
    default: Any = None,
    default_factory: Callable = None,
    **kwargs
) -> Any

Custom Operators

All operators return functions that can be used with RxPY's pipe() method:

  • where_field(field_name: str): Filter events by field name
  • where_event_type(event_type: EventType): Filter events by type
  • map_to_value(): Extract the new value from field change events
  • debounce_changes(duration: float): Debounce field changes
  • buffer_changes(count: int): Buffer field changes

Examples

Check out the examples/ directory for complete working examples:

  • basic_usage.py: Basic reactive model usage
  • advanced_usage.py: Advanced features and patterns

Requirements

  • Python 3.8+
  • Pydantic v2.0+
  • RxPY (reactivex) v4.0+

Development

To set up for development:

git clone https://github.com/yourusername/reactive-pydantic.git
cd reactive-pydantic
poetry install

Run tests:

poetry run pytest

License

MIT License - see LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

MIT License. See LICENSE for details.

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

reactive_pydantic-0.1.0.tar.gz (5.8 kB view details)

Uploaded Source

Built Distribution

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

reactive_pydantic-0.1.0-py3-none-any.whl (7.4 kB view details)

Uploaded Python 3

File details

Details for the file reactive_pydantic-0.1.0.tar.gz.

File metadata

  • Download URL: reactive_pydantic-0.1.0.tar.gz
  • Upload date:
  • Size: 5.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.1.3 CPython/3.12.3 Linux/6.6.87.1-microsoft-standard-WSL2

File hashes

Hashes for reactive_pydantic-0.1.0.tar.gz
Algorithm Hash digest
SHA256 0a4f902fbe1a950920483f3f78642ca2a448158aaaf1e450683e367af220aeac
MD5 8209a9c55ddab01d7aad7efff91a67ab
BLAKE2b-256 43d207342c853e4612fbe3cefaa06da7f603c54864ca1a580527bfb75a445e0f

See more details on using hashes here.

File details

Details for the file reactive_pydantic-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: reactive_pydantic-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 7.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.1.3 CPython/3.12.3 Linux/6.6.87.1-microsoft-standard-WSL2

File hashes

Hashes for reactive_pydantic-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1f8e351e3ad11ef6282fce63e604db3cc58e885bf5b4096d05620d6a1b53ac09
MD5 df952b914709898ec017aff3ca03d3de
BLAKE2b-256 4a6284063194eeb8298881b37698571f53827c5cb53ae5c9626ac0db7dd8e285

See more details on using hashes here.

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