Architecture¶
dsviper-components is what every Commit-based application built on
dsviper ends up needing: the standard administrative, collaborative
and scripting surface of a Commit application, already implemented and
ready to compose into a domain-specific UI. Rather than a collection of
loose widgets, it is a coherent set of components that share two
conventions: signal-based state updates and constructor-based
store injection.
The Notifier Bridge¶
See also
This page describes the Python / Qt implementation. For the
language-agnostic statement of the same pattern — UI-framework
notifications layered over the runtime’s CommitStoreNotifying
interface, with parallel adapters for AppKit, Qt C++ and Python — see
DSM — The Notifier Pattern.
The dsviper runtime exposes change notifications through the
CommitStoreNotifying protocol — a plain Python interface with methods
like notify_state_did_change() and notify_database_did_open().
For Qt to consume these notifications, they must become Qt Signals.
DSCommitStoreNotifier is the adapter that bridges the two worlds.
It is a QObject whose signals mirror the protocol’s notification
methods one-for-one:
# dsviper_components/ds_commit_store_notifier.py
class DSCommitStoreNotifier(QObject):
# Database
database_did_open = Signal()
database_did_close = Signal()
state_did_change = Signal()
definitions_did_change = Signal()
# Dispatch
dispatch_error = Signal(Error)
# Live Mode
stop_live = Signal()
# Reset
reset_database = Signal()
database_will_reset = Signal()
database_did_reset = Signal()
message = Signal(str)
def notify_state_did_change(self):
self.state_did_change.emit()
# … one method per Signal
The pattern is the same on every platform that has its own notification
mechanism (NSNotificationCenter on AppKit, signals/slots on Qt C++,
QObject Signals here). The runtime stays platform-agnostic; each UI
adapter translates the abstract notifications into the platform’s
native event mechanism.
Composition over inheritance¶
Every widget in the library accepts the running CommitStore (or one of
its lower-level cousins like Databasing) as a constructor argument:
# A typical dialog
class DSCommitsDialog(QDialog):
def __init__(self, store: CommitStore, *args, **kwargs):
super().__init__(*args, **kwargs)
component = self._setup_ui()
component.set_store(store)
The widget does not subclass anything domain-specific; it does not reach out to a global. The application owns the store and injects it into each component. This makes the library equally usable in:
a single-store application (one
CommitStore.instance(), used by ge-py),a multi-store application (one
CommitStoreper document, used by ge-qml),and headless tools that build a
CommitStorefor a one-off task.
Tip
The instance() classmethods present on some components
(DSCommitStoreNotifier.instance()) are a convenience for single-store
applications, not a requirement. ge-qml deliberately avoids them and
injects an explicit notifier into every component.
Integration recipe¶
A minimal Commit-based application built on dsviper-components does
five things, in this order:
from dsviper import CommitStore, CommitDatabase
from dsviper_components.ds_commit_store_notifier import DSCommitStoreNotifier
from dsviper_components.ds_commits_dialog import DSCommitsDialog
from dsviper_components.ds_commit_undo_dialog import DSCommitUndoDialog
# … any other dialog the application wants
# 1. Build the store (one or many).
store = CommitStore()
# 2. Wire the notifier so the runtime can emit Qt Signals.
notifier = DSCommitStoreNotifier()
store.set_notifier(notifier) # CommitStore consumes CommitStoreNotifying
# 3. Open or create the database, then attach it to the store.
database = CommitDatabase.open("model.cdb")
store.set_database(database)
store.notify_database_did_open()
# 4. Instantiate the dialogs the application wants. Each receives the
# same store. They subscribe to the notifier on their own and react
# to state_did_change, database_did_open, etc.
commits_dialog = DSCommitsDialog(store)
undo_dialog = DSCommitUndoDialog(store)
# 5. Wire the application's own UI to the same notifier, so the domain
# panels redraw in lock-step with the shared dialogs.
notifier.state_did_change.connect(self._refresh_my_panel)
Steps 1–3 are the runtime side. Step 4 is what dsviper-components
adds: every shared widget instantiated with the running store. Step 5
is the only application-specific wiring left.
Note
The notifier is subscribed in two directions: the runtime tells it “state changed”, and the UI listens for the corresponding Qt Signal. The notifier never reaches into the runtime — it only translates notifications.
Why this matters¶
The library’s value is not “widgets you can re-use”. It is the
realisation that the administrative surface of a Commit application —
DAG browsing, undo navigation, blob inspection, remote sync, embedded
scripting — is identical across applications, because it operates
on the runtime concepts, not on any specific domain. Adopting
dsviper-components means delegating that entire surface to the
shared library and writing only the part that is genuinely
domain-specific.
For the catalogue of what the library exposes, see Widgets. For a worked example of the integration in a real application, see the ge-py walk-through.