Skip to content

Add event-driven dashboard stats with product event tracking - #17

Merged
antosubash merged 7 commits into
mainfrom
claude/implement-event-bus-9Sv17
Apr 14, 2026
Merged

Add event-driven dashboard stats with product event tracking#17
antosubash merged 7 commits into
mainfrom
claude/implement-event-bus-9Sv17

Conversation

@antosubash

Copy link
Copy Markdown
Owner

Summary

Implements an event-driven architecture for the Dashboard module to track product lifecycle events (create, update, delete) without direct coupling to the Products module. The EventBus is refactored to use pyee's AsyncIOEventEmitter for more robust async event handling.

Key Changes

Event Bus Refactoring

  • Replaced custom event handler dictionary with pyee.asyncio.AsyncIOEventEmitter for more reliable async event dispatch
  • Updated publish() to use asyncio.gather() for concurrent handler execution with error isolation
  • Updated publish_nowait() to use pyee's native event emission
  • Added error handler for background task failures

Product Events & Contracts

  • Created products/contracts/events.py with domain events: ProductCreated, ProductUpdated, ProductDeleted
  • Exported events from products/contracts/__init__.py for cross-module consumption
  • Updated Products API endpoints to publish events after successful operations (create, update, delete)
  • Added get_event_bus() dependency in products/deps.py to inject EventBus into endpoints

Dashboard Module Implementation

  • Created dashboard/handlers.py with event handlers that maintain in-memory counters for product events
  • Created dashboard/endpoints/api.py with /stats endpoint returning product event counts
  • Updated DashboardModule to:
    • Register event handlers via register_event_handlers()
    • Set route prefix and module dependencies
    • Include API router
  • Added Dashboard dependency on Products module

Testing

  • Added comprehensive test suite (modules/dashboard/tests/test_dashboard.py) covering:
    • Individual handler unit tests
    • Module registration and event subscription
    • Stats API endpoint behavior and authentication
    • End-to-end integration tests (product CRUD → event bus → dashboard counters)
    • Edge cases (failed operations don't emit events, concurrent handler execution)
  • Added EventBus tests for subclass event matching, concurrent dispatch, and edge cases

Dependencies

  • Added pyee>=12.0 to framework/core dependencies
  • Added products module as dependency for dashboard module

Notable Implementation Details

  • Event handlers are strictly type-matched (subclass events don't trigger parent handlers)
  • Failed product operations (404s) don't publish events—handlers execute after successful lookups
  • Dashboard stats are read-only snapshots to prevent external mutation
  • All event handlers run concurrently for better performance
  • Error isolation ensures one handler failure doesn't prevent others from executing

https://claude.ai/code/session_01KLjCnJoNbCbk8is6pVZcom

claude added 7 commits April 13, 2026 21:55
Replace the custom EventBus with a pyee-backed AsyncIOEventEmitter.
Products module now publishes ProductCreated/Updated/Deleted events,
and Dashboard subscribes to them to track activity stats via a new
/api/dashboard/stats endpoint.

https://claude.ai/code/session_01KLjCnJoNbCbk8is6pVZcom
Keep both RequiresPermission decorators from main and EventBus
event publishing from the feature branch in product API endpoints.
Fix import ordering in app_builder and middleware from main.

https://claude.ai/code/session_01KLjCnJoNbCbk8is6pVZcom
Covers dashboard handlers, /api/dashboard/stats, and end-to-end
Product API → EventBus → Dashboard handler wiring. Adds EventBus
tests for subclass isolation, orphan events, and concurrent dispatch.

https://claude.ai/code/session_01KLjCnJoNbCbk8is6pVZcom
…layer removal)

Port event-bus feature onto main's new package layout:
- sm_products → products, sm_dashboard → dashboard
- src/ directory layer removed (modules/<name>/<package>)
- Dashboard depends on 'products' (was sm-products)
- Update all imports in events.py, handlers.py, dashboard endpoints,
  and test_dashboard.py
- Drop the dashboard tests __init__.py that collided with the new
  top-level tests/integration package
- Resolve app_builder.py import conflict in favor of main's
  Request/Response (our RedirectResponse was unused)

https://claude.ai/code/session_01KLjCnJoNbCbk8is6pVZcom
- Use event class objects directly as pyee keys; drop _event_key()
  (pyee accepts any hashable, saves an f-string on every subscribe/publish)
- Hoist register_event_handlers imports to dashboard/module.py top
  (no cycle risk, no FastAPI side effects)
- Drop trivial docstrings on Product event dataclasses (module docstring
  + class names already convey the meaning)
- Drop narrating test comments and redundant class docstrings

Architectural findings (await publish → outbox, service-layer publish,
SQL-derived /stats) deferred per prior instruction.

https://claude.ai/code/session_01KLjCnJoNbCbk8is6pVZcom
Take main's phrasing (#16 added a CI workflow; the conflict is only
in the top-of-file docstring line-length fix — both sides shortened
the same overlong docstring differently).

https://claude.ai/code/session_01KLjCnJoNbCbk8is6pVZcom
- Reinstate ``_event_key()`` so pyee calls receive str, matching its
  stubs (ty caught the stub mismatch on CI). The class-as-key
  simplification was a false-positive — runtime accepts it but the
  typed API does not.
- Apply ruff format to test_dashboard.py (CI check).

https://claude.ai/code/session_01KLjCnJoNbCbk8is6pVZcom
@antosubash
antosubash merged commit c5e3b34 into main Apr 14, 2026
6 checks passed
antosubash pushed a commit that referenced this pull request Apr 14, 2026
Integrates the pyee-backed EventBus and event-driven dashboard stats
(#17) with this branch's DX hardening work.

Conflict resolutions:

- framework/core/simple_module_core/events.py
  Took main's pyee-backed implementation wholesale. My DX fix added an
  MRO walk to deliver subclass events to base-class subscribers and
  switched publish_nowait to asyncio.get_running_loop(). Main took a
  different direction: exact-type dispatch (a new test explicitly
  asserts `test_subclass_events_do_not_match_parent_subscription`) and
  delegates loop handling to pyee. The MRO feature is incompatible
  with that design; dropped it. publish_nowait's loop concern is now
  encapsulated by pyee.

- modules/dashboard/dashboard/module.py
  Combined both sides' ModuleMeta changes:
  * view_prefix="/dashboard"           (this branch, task 7)
  * route_prefix="/api/dashboard"      (main, for /api/dashboard/stats)
  * depends_on=["Products"]            (main, for event-order dependency)

No changes to tests; 267/267 still pass, ruff/ty/biome/tsc all clean.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants