feat(broadcasting): BroadcastEvent.emit(), Broadcast facade, ChannelAuthRegistry, ReverbProvider auto-wiring#112
Conversation
…uthRegistry, ReverbProvider routes
Implements tasks #147, #148, #149, #150.
## BroadcastEvent (Task #147)
- Add `payload: dict` and `name: str | None` class-level attributes
- `broadcast_as()` uses `self.name` when set, falls back to class name
- `broadcast_with()` uses `self.payload` when non-empty, falls back to instance attrs
- Add `emit()` method — delegates to `Broadcast.dispatch(self)`
## Broadcast facade + ChannelAuthRegistry (Task #148)
- New `broadcasting/auth.py` — `ChannelAuthRegistry` with `{wildcard}` pattern
matching, type-hint casting, and async-callback support
- `BroadcastManager` gains `dispatch()`, `emit()`, and `channel()` decorator
- Fix `Broadcast` facade key from `"broadcast"` → `"broadcasting"`
- Rewrite `Broadcast.pyi` stub with full typed API
## ReverbProvider auto-wiring (Task #149)
- `boot()` now mounts the Pusher-protocol WebSocket at `/app/{app_key}`
- Mounts `POST /broadcasting/auth` for Laravel Echo auth handshake
- Auto-loads `routes/channels.py` from the application base path
- Publishes `stubs/channels.py` as `routes/channels.py` via `artisan publish`
## routes/channels.py stub (Task #150)
- Add `broadcasting/stubs/channels.py` to the framework package (publishable)
Tests: 18 new tests covering emit(), ChannelAuthRegistry, and manager.channel()
All 869 suite tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Code Review — Request ChangesOverall this is a solid implementation that correctly extends the existing 🔴 Blocker 1 —
|
| # | Severity | File | Issue |
|---|---|---|---|
| 1 | 🔴 Blocking | broadcasting/event.py |
emit() must be async def with await Broadcast.dispatch(self) |
| 2 | 🔴 Blocking | tests/broadcasting/test_event_emit.py |
Use AsyncMock + assert_awaited_once_with |
| 3 | 🟡 Minor | broadcasting/channels.py |
Add __eq__ / __hash__ to Channel |
| 4 | 🟡 Minor | broadcasting/provider.py |
Safe user resolution in auth endpoint |
Please fix items 1–2 before merge. Items 3–4 are highly recommended.
Additional Required Change — Task #155: Remove Broadcast facadeNew blocking requirement added by PM (Task #155): The framework does not use facades for broadcasting. The 🔴 Blocker 5 — Remove
|
| # | Issue |
|---|---|
| 1 | emit() must be async def with await |
| 2 | Test must use AsyncMock + assert_awaited_once_with |
| 3 | Add __eq__/__hash__ to Channel |
| 4 | Fix fragile user resolution in /broadcasting/auth |
| 5 | Remove facades/Broadcast.py + .pyi; replace with direct container access |
…uality, direct channel decorator
- Remove facades/Broadcast.py and Broadcast.pyi entirely; remove from
facades/__init__.py — no Broadcast facade in the broadcasting package
- Make BroadcastEvent.emit() async; resolves BroadcastManager directly
from the Application container (app().make("broadcasting")), no facade
- Add module-level channel() decorator in helpers.py, exported from
fastapi_startkit.broadcasting — users now import it directly:
`from fastapi_startkit.broadcasting import channel`
- Add __eq__ and __hash__ to Channel so two Channel instances with the
same name compare equal and are usable in sets / as dict keys
- Fix /broadcasting/auth user resolution: check callable(user_attr)
before calling it, avoiding TypeError on property-based auth services
- Update stubs/channels.py to use `from fastapi_startkit.broadcasting import channel`
- Fix test_emit_calls_broadcast_dispatch: use AsyncMock for async emit(),
patch at the module attribute level (event.app), add @pytest.mark.asyncio
- Add Channel __eq__/__hash__ tests to test_channels.py
- Update all docstrings that referenced the Broadcast facade
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
This PR completes task #155 (remove Broadcast facade) plus reviewer-identified blocking fixes:
Blocking fixes (from code review)
BroadcastEvent.emit()is now async and resolvesBroadcastManagerdirectly from the Application container (app().make("broadcasting")) — no facade in the call chain. Events were previously silently dropped becauseemit()was sync but called an async dispatcher.test_emit_calls_broadcast_dispatchnow usesAsyncMockfor the asyncdispatch, patches atfastapi_startkit.broadcasting.event.app(module-level attribute), and is decorated with@pytest.mark.asyncio.Facade removal (task #155)
facades/Broadcast.pyandfacades/Broadcast.pyiBroadcastimport fromfacades/__init__.pyNew direct API
channel()decorator exported fromfastapi_startkit.broadcasting— users import it asfrom fastapi_startkit.broadcasting import channelinstead of via a facadestubs/channels.pyupdated to use the new direct importChannel equality (reviewer minor)
__eq__and__hash__toChannel— two instances with the same name are now equal and usable in sets / as dict keystest_channels.pyAuth endpoint robustness (reviewer minor)
/broadcasting/authuser resolution now checkscallable(user_attr)before calling it, avoidingTypeErrorwhen the auth service exposesuseras a property rather than a methodTest plan
pytest tests/broadcasting/ -v)ruff checkandruff format --checkclean🤖 Generated with Claude Code