--- title: "Blinker: Fast Signal/Event Dispatching System" library_name: blinker pypi_package: blinker category: event-system python_compatibility: "3.8+" last_updated: "2025-11-02" official_docs: "https://blinker.readthedocs.io" official_repository: "https://github.com/pallets-eco/blinker" maintenance_status: "active" --- # Blinker: Fast Signal/Event Dispatching System ## Official Information **Repository:** **PyPI Package:** blinker **Current Version:** 1.9.0 (Released 2024-11-08) **Official Documentation:** **License:** MIT License **Maintenance Status:** Active (Pallets Community Ecosystem) @source @source @source ## Core Purpose Blinker provides a fast dispatching system that allows any number of interested parties to subscribe to events or "signals". It implements the Observer pattern with a clean, Pythonic API. ### Problem Space Without blinker, you would need to manually implement: - Global event registries for decoupled components - Weak reference management for automatic cleanup - Thread-safe event dispatching - Sender-specific event filtering - Return value collection from multiple handlers ### When to Use Blinker **Use blinker when:** - Building plugin systems that need event hooks - Implementing application lifecycle hooks (like Flask) - Creating decoupled components that communicate via events - Building event-driven architectures within a single process - Need multiple independent handlers for the same event - Want automatic cleanup via weak references **What you would be "reinventing the wheel" without it:** - Observer/subscriber pattern implementation - Named signal registries for plugin communication - Weak reference management for receivers - Thread-safe signal dispatching - Sender filtering and context passing ## Python Version Compatibility **Minimum Python Version:** 3.9+ **Python 3.11:** Fully compatible **Python 3.12:** Fully compatible **Python 3.13:** Fully compatible **Python 3.14:** Expected to be compatible @source ### Thread Safety Blinker signals are thread-safe. The library uses weak references for automatic cleanup and properly handles concurrent signal emission and subscription. ## Integration Patterns ### Flask Ecosystem Integration Flask uses blinker as its signal system foundation. Flask provides built-in signals like: - `request_started` - Before request processing begins - `request_finished` - After response is constructed - `template_rendered` - When template is rendered - `request_tearing_down` - During request teardown @source **Example Flask Signal Usage:** ```python from flask import template_rendered def log_template_renders(sender, template, context, **extra): sender.logger.info( f"Rendered {template.name} with context {context}" ) template_rendered.connect(log_template_renders, app) ``` ### Event-Driven Architecture Blinker excels at creating loosely coupled components: ```python from blinker import Namespace # Create isolated namespace for your application app_signals = Namespace() # Define signals user_logged_in = app_signals.signal('user-logged-in') data_updated = app_signals.signal('data-updated') # Multiple handlers can subscribe @user_logged_in.connect def update_last_login(sender, **kwargs): user_id = kwargs.get('user_id') # Update database @user_logged_in.connect def send_login_notification(sender, **kwargs): # Send email notification pass # Emit signal user_logged_in.send(app, user_id=123, ip_address='192.168.1.1') ``` ### Plugin Systems ```python from blinker import signal # Core application defines hook points plugin_loaded = signal('plugin-loaded') before_process = signal('before-process') after_process = signal('after-process') # Plugins subscribe to hooks @before_process.connect def plugin_preprocess(sender, data): # Plugin modifies data before processing return data # Application emits signals at hook points results = before_process.send(self, data=input_data) for receiver, result in results: if result is not None: input_data = result ``` ## Real-World Examples ### Example 1: Flask Request Monitoring @source (Flask instrumentation with blinker) ```python from flask import request_started, request_finished import time request_times = {} def track_request_start(sender, **extra): request_times[id(extra)] = time.time() def track_request_end(sender, response, **extra): duration = time.time() - request_times.pop(id(extra), time.time()) sender.logger.info(f"Request took {duration:.2f}s") request_started.connect(track_request_start) request_finished.connect(track_request_end) ``` ### Example 2: Model Save Hooks @source ```python from blinker import Namespace model_signals = Namespace() model_saved = model_signals.signal('model-saved') class Model: def save(self): # Save to database self._persist() # Emit signal for observers model_saved.send(self, model_type=self.__class__.__name__) # Cache invalidation handler @model_saved.connect def invalidate_cache(sender, **kwargs): cache.delete(f"model:{kwargs['model_type']}") # Audit logging handler @model_saved.connect def log_change(sender, **kwargs): audit_log.write(f"Model saved: {kwargs['model_type']}") ``` ### Example 3: Sender-Specific Subscriptions @source README ```python from blinker import signal round_started = signal('round-started') # General subscriber - receives from all senders @round_started.connect def each_round(sender): print(f"Round {sender}") # Sender-specific subscriber - only for sender=2 @round_started.connect_via(2) def special_round(sender): print("This is round two!") for round_num in range(1, 4): round_started.send(round_num) # Output: # Round 1 # Round 2 # This is round two! # Round 3 ``` ### Example 4: Async Signal Handlers @source ```python import asyncio from blinker import Signal async_signal = Signal() # Async receiver async def async_receiver(sender, **kwargs): await asyncio.sleep(1) print("Async handler completed") async_signal.connect(async_receiver) # Send to async receivers await async_signal.send_async() # Mix sync and async receivers def sync_receiver(sender, **kwargs): print("Sync handler") async_signal.connect(sync_receiver) # Provide wrapper for sync handlers in async context async def sync_wrapper(func): async def inner(*args, **kwargs): func(*args, **kwargs) return inner await async_signal.send_async(_sync_wrapper=sync_wrapper) ``` ## Usage Examples ### Basic Signal Definition and Connection ```python from blinker import signal # Named signals (shared across modules) initialized = signal('initialized') # Anonymous signals (class attributes) from blinker import Signal class Processor: on_ready = Signal() on_complete = Signal() def process(self): self.on_ready.send(self) # Do work self.on_complete.send(self, status='success') # Connect receivers @initialized.connect def on_init(sender, **kwargs): print(f"Initialized by {sender}") processor = Processor() @processor.on_complete.connect def handle_completion(sender, **kwargs): print(f"Status: {kwargs['status']}") ``` ### Named Signals for Decoupling ```python from blinker import signal # Module A defines and sends def user_service(): user_created = signal('user-created') # Create user user_created.send('user_service', user_id=123, username='john') # Module B subscribes (no import of Module A needed!) def notification_service(): user_created = signal('user-created') # Same signal instance @user_created.connect def send_welcome_email(sender, **kwargs): print(f"Sending email to {kwargs['username']}") ``` ### Checking for Receivers Before Expensive Operations ```python from blinker import signal data_changed = signal('data-changed') def update_data(new_data): # Only compute expensive stats if someone is listening if data_changed.receivers: stats = compute_expensive_stats(new_data) data_changed.send(self, data=new_data, stats=stats) else: # Skip expensive computation data_changed.send(self, data=new_data) ``` ### Temporarily Muting Signals (Testing) ```python from blinker import signal send_email = signal('send-email') @send_email.connect def actually_send(sender, **kwargs): # Send real email pass def test_user_registration(): # Don't send emails during tests with send_email.muted(): register_user('test@example.com') # send_email signal is ignored in this context ``` ### Collecting Return Values ```python from blinker import signal validate_data = signal('validate-data') @validate_data.connect def check_email(sender, **kwargs): email = kwargs['email'] if '@' not in email: return False, "Invalid email" return True, None @validate_data.connect def check_username(sender, **kwargs): username = kwargs['username'] if len(username) < 3: return False, "Username too short" return True, None # Collect all validation results results = validate_data.send( None, email='invalid', username='ab' ) for receiver, (valid, error) in results: if not valid: print(f"Validation failed: {error}") ``` ## When NOT to Use Blinker ### Scenario 1: Simple Callbacks Sufficient **Don't use blinker when:** - Single callback function is enough - No need for dynamic subscription/unsubscription - Callbacks are tightly coupled to caller ```python # Overkill - use simple callback from blinker import signal sig = signal('done') sig.connect(on_done) sig.send(self) # Better - direct callback def process(callback): # do work callback() process(on_done) ``` ### Scenario 2: Async Event Systems **Don't use blinker when:** - Building async-first distributed event system - Need message queuing and persistence - Cross-process or cross-network communication ```python # Wrong tool - blinker is in-process only from blinker import signal distributed_event = signal('cross-service-event') # Better - use async message queue import asyncio from aio_pika import connect, Message async def publish_event(): connection = await connect("amqp://guest:guest@localhost/") channel = await connection.channel() await channel.default_exchange.publish( Message(b"event data"), routing_key="events" ) ``` ### Scenario 3: Complex State Machines **Don't use blinker when:** - Need state transitions with guards and actions - Require hierarchical or concurrent states - Complex workflow orchestration ```python # Wrong tool - too complex for simple signals from blinker import signal # Better - use state machine library from transitions import Machine class Order: states = ['pending', 'paid', 'shipped', 'delivered'] def __init__(self): self.machine = Machine( model=self, states=Order.states, initial='pending' ) self.machine.add_transition('pay', 'pending', 'paid') self.machine.add_transition('ship', 'paid', 'shipped') ``` ### Scenario 4: Request/Response Patterns **Don't use blinker when:** - Need bidirectional request/response communication - Require RPC-style method calls - Need return values from specific handlers ```python # Awkward with signals result = some_signal.send(self, request='data') # Hard to know which handler provided what # Better - direct method call or dependency injection class ServiceLocator: def get_service(self, name): return self._services[name] service = locator.get_service('data_processor') result = service.process(data) ``` ## Decision Guidance Matrix | Use Blinker When | Use Callbacks When | Use AsyncIO When | Use Message Queue When | | --- | --- | --- | --- | | Multiple independent handlers needed | Single handler sufficient | Async/await throughout codebase | Cross-process communication needed | | Plugin system with dynamic handlers | Tightly coupled components | I/O-bound async operations | Message persistence required | | Decoupled modules need communication | Callback logic is simple | Event loop already present | Distributed systems | | Framework-level hooks (like Flask) | Direct function call works | Concurrent async tasks | Reliability and retry needed | | Observable events in OOP design | Inline lambda sufficient | Network I/O heavy | Message ordering matters | | Weak reference cleanup needed | Manual lifecycle management OK | WebSockets/long-lived connections | Load balancing across workers | ### Decision Tree ```text Need event notifications? ├─ Single process only? │ ├─ YES: Continue │ └─ NO: Use message queue (RabbitMQ, Redis, Kafka) │ ├─ Multiple handlers per event? │ ├─ YES: Continue │ └─ NO: Use simple callback function │ ├─ Handlers need to be dynamic (plugins)? │ ├─ YES: Use Blinker ✓ │ └─ NO: Direct method calls may suffice │ ├─ Async/await heavy codebase? │ ├─ YES: Consider asyncio event system │ │ (or use Blinker with send_async) │ └─ NO: Use Blinker ✓ │ └─ Need weak reference cleanup? ├─ YES: Use Blinker ✓ └─ NO: Simple callbacks OK ``` ## Installation ```bash pip install blinker ``` Current version: 1.9.0 Minimum Python: 3.9+ @source ## Key Features - **Global named signal registry:** `signal('name')` returns same instance everywhere - **Anonymous signals:** Create isolated `Signal()` instances - **Sender filtering:** `connect(handler, sender=obj)` for sender-specific subscriptions - **Weak references:** Automatic cleanup when receivers are garbage collected - **Thread safety:** Safe for concurrent use - **Return value collection:** Gather results from all handlers - **Async support:** `send_async()` for coroutine receivers - **Temporary connections:** Context managers for scoped subscriptions - **Signal muting:** Disable signals temporarily (useful for testing) @source ## Common Pitfalls 1. **Memory leaks with strong references:** ```python # Default uses weak references - OK signal.connect(handler) # Strong reference - prevents garbage collection signal.connect(handler, weak=False) # Use sparingly! ``` 2. **Expecting signals to modify behavior:** - Signals are for observation, not control flow - Don't rely on signal handlers to prevent actions - Use explicit validation/authorization instead 3. **Forgetting sender parameter:** ```python @my_signal.connect def handler(sender, **kwargs): # sender is required! print(kwargs['data']) ``` 4. **Cross-process communication:** - Blinker is in-process only - Use message queues for distributed systems 5. **Performance with many handlers:** - Check `signal.receivers` before expensive operations - Consider limiting number of subscribers for hot paths ## Related Libraries - **Django Signals:** Built into Django, similar concept but Django-specific - **PyPubSub:** More complex publish-subscribe system - **asyncio events:** For async-first applications - **RxPY:** Reactive extensions for Python (more powerful, more complex) - **Celery:** For distributed task queues and async workers ## Summary Blinker is the standard solution for in-process event dispatching in Python, particularly within the Pallets ecosystem (Flask). Use it when you need clean, decoupled event notifications between components in the same process. For distributed systems, async-heavy codebases, or simple single-callback scenarios, consider alternatives. **TL;DR:** Blinker = Observer pattern done right, with weak references, thread safety, and a clean API. Essential for Flask signals and plugin systems.