Add event-driven dashboard stats with product event tracking - #17
Merged
Conversation
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
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
pyee.asyncio.AsyncIOEventEmitterfor more reliable async event dispatchpublish()to useasyncio.gather()for concurrent handler execution with error isolationpublish_nowait()to use pyee's native event emissionProduct Events & Contracts
products/contracts/events.pywith domain events:ProductCreated,ProductUpdated,ProductDeletedproducts/contracts/__init__.pyfor cross-module consumptionget_event_bus()dependency inproducts/deps.pyto inject EventBus into endpointsDashboard Module Implementation
dashboard/handlers.pywith event handlers that maintain in-memory counters for product eventsdashboard/endpoints/api.pywith/statsendpoint returning product event countsDashboardModuleto:register_event_handlers()Testing
modules/dashboard/tests/test_dashboard.py) covering:Dependencies
pyee>=12.0to framework/core dependenciesproductsmodule as dependency for dashboard moduleNotable Implementation Details
https://claude.ai/code/session_01KLjCnJoNbCbk8is6pVZcom