7.6 KiB
Base model route architecture
The model routing stack now splits HTTP wiring, orchestration logic, and business rules into discrete layers. The goal is to make it obvious where a new collaborator should live and which contract it must honour. The diagram below captures the end-to-end flow for a typical request:
graph TD
subgraph HTTP
A[ModelRouteRegistrar] -->|binds| B[BaseModelRoutes handler proxy]
end
subgraph Application
B --> C[ModelHandlerSet]
C --> D1[Handlers]
D1 --> E1[Use cases]
E1 --> F1[Services / scanners]
end
subgraph Side Effects
F1 --> G1[Cache & metadata]
F1 --> G2[Filesystem]
F1 --> G3[WebSocket state]
end
Every box maps to a concrete module:
| Layer | Module(s) | Responsibility |
|---|---|---|
| Registrar | py/routes/model_route_registrar.py |
Declarative list of routes shared by every model type and helper methods for binding them to an aiohttp application. |
| Route controller | py/routes/base_model_routes.py |
Constructs the handler graph, injects shared services, exposes proxies that surface 503 Service not ready when the model service has not been attached. |
| Handler set | py/routes/handlers/model_handlers.py |
Thin HTTP adapters grouped by concern (page rendering, listings, mutations, queries, downloads, CivitAI integration, move operations, auto-organize). |
| Use cases | py/services/use_cases/*.py |
Encapsulate long-running flows (DownloadModelUseCase, BulkMetadataRefreshUseCase, AutoOrganizeUseCase). They normalise validation errors and concurrency constraints before returning control to the handlers. |
| Services | py/services/*.py |
Existing services and scanners that mutate caches, write metadata, move files, and broadcast WebSocket updates. |
Handler responsibilities & contracts
ModelHandlerSet flattens the handler objects into the exact callables used by
the registrar. The table below highlights the separation of concerns within
the set and the invariants that must hold after each handler returns.
| Handler | Key endpoints | Collaborators | Contracts |
|---|---|---|---|
ModelPageView |
/{prefix} |
SettingsManager, server_i18n, Jinja environment, service.scanner |
Template is rendered with is_initializing flag when caches are cold; i18n filter is registered exactly once per environment instance. |
ModelListingHandler |
/api/lm/{prefix}/list |
service.get_paginated_data, service.format_response |
Listings respect pagination query parameters and cap page_size at 100; every item is formatted before response. |
ModelManagementHandler |
Mutations (delete, exclude, metadata, preview, tags, rename, bulk delete, duplicate verification) | ModelLifecycleService, MetadataSyncService, PreviewAssetService, TagUpdateService, scanner cache/index |
Cache state mirrors filesystem changes: deletes prune cache & hash index, preview replacements synchronise metadata and cache NSFW levels, metadata saves trigger cache resort when names change. |
ModelQueryHandler |
Read-only queries (top tags, folders, duplicates, metadata, URLs) | Service query helpers & scanner cache | Outputs always wrapped in {"success": True} when no error; duplicate/filename grouping omits empty entries; invalid parameters (e.g. missing model_root) return HTTP 400. |
ModelDownloadHandler |
/api/lm/download-model, /download-model-get, /download-progress/{id}, /cancel-download-get |
DownloadModelUseCase, DownloadCoordinator, WebSocketManager |
Payload validation errors become HTTP 400 without mutating download progress cache; early-access failures surface as HTTP 401; successful downloads cache progress snapshots that back both WebSocket broadcasts and polling endpoints. |
ModelCivitaiHandler |
CivitAI metadata routes | MetadataSyncService, metadata provider factory, BulkMetadataRefreshUseCase |
fetch_all_civitai streams progress via WebSocketBroadcastCallback; version lookups validate model type before returning; local availability fields derive from hash lookups without mutating cache state. |
ModelMoveHandler |
move_model, move_models_bulk |
ModelMoveService |
Moves execute atomically per request; bulk operations aggregate success/failure per file set. |
ModelAutoOrganizeHandler |
/api/lm/{prefix}/auto-organize (GET/POST), /auto-organize-progress |
AutoOrganizeUseCase, WebSocketProgressCallback, WebSocketManager |
Enforces single-flight execution using the shared lock; progress broadcasts remain available to polling clients until explicitly cleared; conflicts return HTTP 409 with a descriptive error. |
Use case boundaries
Each use case exposes a narrow asynchronous API that hides the underlying services. Their error mapping is essential for predictable HTTP responses.
| Use case | Entry point | Dependencies | Guarantees |
|---|---|---|---|
DownloadModelUseCase |
execute(payload) |
DownloadCoordinator.schedule_download |
Translates ValueError into DownloadModelValidationError for HTTP 400, recognises early-access errors ("401" in message) and surfaces them as DownloadModelEarlyAccessError, forwards success dictionaries untouched. |
AutoOrganizeUseCase |
execute(file_paths, progress_callback) |
ModelFileService.auto_organize_models, WebSocketManager lock |
Guarded by ws_manager lock + status checks; raises AutoOrganizeInProgressError before invoking the file service when another run is already active. |
BulkMetadataRefreshUseCase |
execute_with_error_handling(progress_callback) |
MetadataSyncService, SettingsManager, WebSocketBroadcastCallback |
Iterates through cached models, applies metadata sync, emits progress snapshots that handlers broadcast unchanged. |
Maintaining legacy contracts
The refactor preserves the invariants called out in the previous architecture notes. The most critical ones are reiterated here to emphasise the collaboration points:
- Cache mutations – Delete, exclude, rename, and bulk delete operations are
channelled through
ModelManagementHandler. The handler delegates toModelLifecycleServiceorMetadataSyncService, and the scanner cache is mutated in-place before the handler returns. The accompanying tests assert thatscanner._cache.raw_dataandscanner._hash_indexstay in sync after each mutation. - Preview updates –
PreviewAssetService.replace_previewwrites the new asset,MetadataSyncServicepersists the JSON metadata, andscanner.update_preview_in_cachemirrors the change. The handler returns the static URL produced byconfig.get_preview_static_url, keeping browser clients in lockstep with disk state. - Download progress –
DownloadCoordinator.schedule_downloadgenerates the download identifier, registers a WebSocket progress callback, and caches the latest numeric progress viaWebSocketManager. Bothdownload_modelresponses and/download-progress/{id}polling read from the same cache to guarantee consistent progress reporting across transports.
Extending the stack
To add a new shared route:
- Declare it in
COMMON_ROUTE_DEFINITIONSusing a unique handler name. - Implement the corresponding coroutine on one of the handlers inside
ModelHandlerSet(or introduce a new handler class when the concern does not fit existing ones). - Inject additional dependencies in
BaseModelRoutes._create_handler_setby wiring services or use cases through the constructor parameters.
Model-specific routes should continue to be registered inside the subclass
implementation of setup_specific_routes, reusing the shared registrar where
possible.