Skip to content

Commit 9dd3ea2

Browse files
committed
sonarcloud review fix
1 parent 1e4b9a3 commit 9dd3ea2

7 files changed

Lines changed: 266 additions & 24 deletions

File tree

.sonarcloud.properties

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Vendored third-party code (PostgreSQL contrib extensions bundled per platform)
2+
# is not ours to analyze.
3+
sonar.exclusions=native-build/*/vendor/**
4+
5+
# Copy-paste detection exclusions:
6+
# - native-build/ holds three intentionally parallel per-platform trees
7+
# (linux/macos/windows supervisor, launcher, env, paths); their similarity
8+
# is by design, not accidental duplication.
9+
# - test/ duplication is repeated setup/fixture boilerplate across tests.
10+
sonar.cpd.exclusions=native-build/**,test/**

refactoring.md

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# AudioMuse-AI — Deep Import Chain Analysis & Refactoring Guide
2+
3+
> Generated 2026-06-12 — full static analysis of all ~60 Python source files in the project.
4+
> Ranking is by **total transitive project modules pulled in** when the file is loaded
5+
> (i.e. "A imports B which imports C which imports D…").
6+
7+
---
8+
9+
## 📊 Top 10 Most Import-Heavy Files
10+
11+
| # | Code File | Direct Internal Imports | Transitive (~) | Impacted Functionality | Suggestion |
12+
|---|-----------|------------------------|---------------------|------------------------|------------|
13+
| **1** | `tasks/analysis.py` | **11** | **~22** | **Audio analysis orchestrator**: ONNX embedding inference, mood prediction, CLAP text-search index, lyrics embedding index, SemGrove merged index, artist GMM index, Voyager HNSW index, media server track/album fetching, CUDA memory cleanup. Touches every ML and data subsystem. | Split into `analysis_orchestrator.py` (RQ entry points + task status) and `analysis_pipeline.py` (worker logic). Move the 5 index-builder imports (`clap_text_search`, `lyrics_manager`, `sem_grove_manager`, `artist_gmm_manager`, `voyager_manager`) to **function-level lazy imports** inside each `build_and_store_*_task()` function, since they are only needed during index builds, not at module load. |
14+
| **2** | `app_setup.py` | **5** | **~20** | **Setup wizard**: media server config (all 5 types), auth fields (JWT, API token, user/password), lyrics API config (2 providers), advanced/enum field dropdowns, hidden fields. The line `from app import app, setup_manager` pulls in **all 18 modules** that `app.py` imports — the entire Flask app, all blueprints, auth, provider migration, error handling, Redis, Swagger. | Change `from app import app, setup_manager``from flask_app import app` + `from tasks.setup_manager import SetupManager`. The `flask_app` singleton already exists for exactly this purpose. Move `check_setup_needed` into a tiny `app_setup_guard.py` that has zero internal imports, so `app.py` can import it without cascading. |
15+
| **3** | `app.py` | **8** | **~18** | **Main Flask entry point**: all ~20 blueprint registrations, authentication init, provider migration, error handling, RQ/Redis connection, Swagger docs, JWT secret resolution, admin seed, proxy fix. | Defer blueprint registration to a `register_blueprints(app)` function called inside `create_app()`. Lazy-import `app_auth` and `app_provider_migration` inside their respective `init_app()` calls rather than at module top. Move the `from rq.job import Job, JobStatus` and `from redis import Redis` imports to `taskqueue.py` where they already belong. |
16+
| **4** | `tasks/clustering.py` | **5** | **~17** | **Playlist clustering pipeline**: KMeans/DBSCAN/GMM/Spectral algorithms, AI-powered playlist naming (via `tasks/ai/api` → 4 LLM providers), media server playlist CRUD (create/delete), GPU-accelerated clustering (RAPIDS cuML), post-processing (duplicate filtering, minimum size filtering, top-N diversity selection). | Already well-refactored into helper/postprocessing modules. Move the 40+ config constant imports into a `ClusteringConfig` dataclass passed as a single parameter. Lazy-import `mediaserver` functions inside the playlist-creation closures rather than at module level. The `from .clustering_helper import (...)` pulls `sklearn`, `commons`, `ai/api`, `ai/prompts`, and `clustering_gpu` — consider lazy-loading the GPU module since it's conditional. |
17+
| **5** | `tasks/song_alchemy.py` | **4** (+2 lazy) | **~14** | **Song Alchemy blending engine**: vector centroid math (weighted blend of song/artist vectors), PCA dimensionality reduction, LDA genre discriminant projection, UMAP 2D projection, artist GMM component weighting, Voyager nearest-neighbor fallback, per-artist cap enforcement. | Extract the 4 projection functions (`_project_with_umap`, `_project_to_2d`, `_project_aligned_add_sub`, `_project_with_discriminant`) into a standalone `tasks/alchemy_projections.py`. This lets `app_map.py` import projections without pulling the full alchemy → voyager → mediaserver chain. Convert the 2 lazy imports (`artist_gmm_manager`, `app_helper_artist`) to **dependency injection** — accept callables as parameters. |
18+
| **6** | `app_map.py` | **2** (+1 lazy) | **~13** | **2D/3D music map visualization**: UMAP projection cache, genre discriminant coloring, JSON/GZip compressed responses at 25/50/75/100% resolutions, mood centroid overlay, per-cluster statistics. | Move the `try: from tasks.song_alchemy import _project_with_umap, ...` block to import from `tasks/alchemy_projections.py` instead (after creating it per suggestion #5). This single change cuts the transitive chain from ~13 to ~4, because `song_alchemy``voyager_manager``mediaserver` → 5 providers is the deepest part of the cascade. |
19+
| **7** | `app_alchemy.py` | **2** | **~12** | **Song Alchemy web UI**: blend page HTML, artist autocomplete API (`/api/search_artists`), alchemy search endpoint. The cascade comes entirely from `from tasks.song_alchemy import song_alchemy`. | This blueprint is already minimal (2 imports). The fix is upstream: after refactoring #5 and #6, the transitive count drops from ~12 to ~3 automatically. No direct changes needed here. |
20+
| **8** | `tasks/mediaserver/__init__.py` | **5** | **~10** | **Media server dispatcher (HUB)**: unified API facade over 5 provider backends. Imports ~80 functions (16 per provider × 5) at module level, unconditionally loading Jellyfin, Navidrome, Lyrion, MPD, and Emby code — even though only ONE provider is active at runtime. Imported by 6+ other modules. | Implement **lazy provider loading**: replace the 5 `from .jellyfin import (...)` / `from .navidrome import (...)` / etc. blocks with a `_provider = None` + `_get_provider()` function that imports only the module matching `config.MEDIASERVER_TYPE` on first call. This cuts module-load cost by 80% and removes 4 unnecessary HTTP session initializations. The public API surface (function names) stays identical. |
21+
| **9** | `app_sonic_fingerprint.py` | **4** | **~10** | **Sonic Fingerprint feature**: generates a "taste profile" vector from the user's most-played tracks (weighted by recency), then finds similar songs via Voyager nearest-neighbor. Also resolves Emby/Jellyfin user credentials, fetches top stratified genres. | The double import of the mediaserver chain is the issue: `from tasks.sonic_fingerprint_manager import ...` already pulls `mediaserver` (+5 providers) internally, and then `from tasks.mediaserver import resolve_emby_jellyfin_user` pulls it again directly. Move the `resolve_emby_jellyfin_user` call into `sonic_fingerprint_manager` so the blueprint only imports one module. The `top_stratified_genre` import from `app_helper` is fine — it's lightweight. |
22+
| **10** | `app_voyager.py` | **3** | **~9** | **Voyager similarity search UI**: song/artist similarity lookups, mood-centroid-based search, radius-bounded nearest-neighbor walk, duplicate elimination, playlist creation from similarity results. Loads the `mood_centroids_real_080_clap.json` file (~1MB) at module level. | The `from tasks.voyager_manager import (...)` pulls `mediaserver` → 5 providers. Since `voyager_manager` only needs `mediaserver` for `create_instant_playlist` (one function), move that import inside the single function that uses it. Also, defer `_load_mood_centroids_for_similarity()` to first API call rather than module import — the 1MB JSON parse adds unnecessary startup latency for every worker process. |
23+
24+
---
25+
26+
## 🔗 The Three Deepest Import Chains
27+
28+
### Chain 1 — The Alchemy Cascade (Depth 5, ~14 modules)
29+
30+
```
31+
app_alchemy.py
32+
└─ tasks/song_alchemy.py
33+
├─ tasks/voyager_manager.py
34+
│ └─ tasks/mediaserver/__init__.py
35+
│ ├─ tasks/mediaserver/jellyfin.py → .http, .helper, config
36+
│ ├─ tasks/mediaserver/navidrome.py → .http, .helper, config
37+
│ ├─ tasks/mediaserver/lyrion.py → .http, .helper, config
38+
│ ├─ tasks/mediaserver/mpd.py → .http, config
39+
│ └─ tasks/mediaserver/emby.py → .http, .helper, config
40+
├─ app_helper.py → database.py, taskqueue.py, config, tz_helper
41+
└─ app_helper_artist.py → app_helper, memory_utils
42+
```
43+
44+
### Chain 2 — The Setup Cascade (Depth 4, ~20 modules)
45+
46+
```
47+
app_setup.py
48+
└─ from app import app, setup_manager ← PULLS ALL OF:
49+
app.py
50+
├─ flask_app.py
51+
├─ app_helper.py → database, taskqueue, config, tz_helper
52+
├─ app_auth.py → flask, config, secrets
53+
├─ app_provider_migration.py → app_helper, mediaserver/helper
54+
├─ error/__init__.py → error_manager → error_dictionary
55+
├─ config.py
56+
└─ tasks/setup_manager.py → config, psycopg2, argon2
57+
```
58+
59+
### Chain 3 — The Clustering Cascade (Depth 4, ~17 modules)
60+
61+
```
62+
app_clustering.py
63+
└─ tasks/clustering.py
64+
├─ tasks/clustering_helper.py
65+
│ ├─ sklearn (KMeans, DBSCAN, Spectral, GMM, PCA, metrics)
66+
│ ├─ tasks/commons.py → config
67+
│ ├─ tasks/ai/api.py → providers/{openai,ollama,gemini,mistral}
68+
│ ├─ tasks/ai/prompts.py → config
69+
│ └─ tasks/clustering_gpu.py → RAPIDS cuML (conditional)
70+
├─ tasks/clustering_postprocessing.py → scipy, psycopg2
71+
├─ tasks/mediaserver/__init__.py → 5 providers
72+
└─ error/ → error_dictionary
73+
```
74+
75+
---
76+
77+
## 🏗️ Import Architecture Layers
78+
79+
### Layer 0 — Foundation (Zero Internal Imports)
80+
`config.py`, `error/error_dictionary.py`, `app_logging.py`, `tz_helper.py`, `flask_app.py`, `tasks/mediaserver/http.py`, `tasks/mediaserver/helper.py`, `tasks/memory_utils.py`, `tasks/commons.py`, `tasks/playlist_ordering.py`, `tasks/radius_walk_helper.py`, `tasks/provider_migration_matcher.py`
81+
82+
### Layer 1 — Web & Entry Points
83+
`app.py`, `rq_worker.py`, `rq_worker_high_priority.py`, `rq_janitor.py`, `restart_manager.py`, `restart_listener.py`, and all 20 `app_*.py` blueprint files
84+
85+
### Layer 2 — Business Logic (THE HUB LAYER)
86+
`app_helper.py` (imported by 15+ files), `tasks/voyager_manager.py` (imported by 8+ files), `tasks/mediaserver/__init__.py` (imported by 6+ files), `tasks/analysis.py`, `tasks/clustering.py`, `error/error_manager.py`
87+
88+
### Layer 3+ — Specialized Handlers
89+
`tasks/song_alchemy.py`, `tasks/clustering_helper.py`, `tasks/clustering_postprocessing.py`, `tasks/analysis_helper.py`, `tasks/path_manager.py`, `tasks/radio_manager.py`, `tasks/ai/tool_impl.py`, `tasks/ai/planner.py`, `tasks/ai/tools.py`
90+
91+
---
92+
93+
## 🎯 Quick Wins (Low Effort, High Impact)
94+
95+
| # | Change | Files Touched | Effort | Impact |
96+
|---|--------|--------------|--------|--------|
97+
| 1 | **Lazy provider loading in mediaserver dispatcher** — Replace 5 unconditional `from .jellyfin import (...)` blocks with a `_get_provider()` function that imports only the active provider at first call. | `tasks/mediaserver/__init__.py` | 🟢 Small | Cuts ~80 import lines, skips 4 unnecessary provider module loads, saves HTTP session init |
98+
| 2 | **Move `create_instant_playlist` import to function body** — In `tasks/voyager_manager.py`, move `from .mediaserver import create_instant_playlist` inside the single function that uses it. | `tasks/voyager_manager.py` | 🟢 Small | Breaks voyager→mediaserver→5 providers chain for all other callers of voyager_manager |
99+
| 3 | **Use `flask_app` singleton in setup** — Change `from app import app``from flask_app import app` in `app_setup.py`. | `app_setup.py` | 🟢 Small | Breaks the app_setup→app→everything cascade; `flask_app` was created exactly for this |
100+
| 4 | **Extract shared projection functions** — Create `tasks/alchemy_projections.py` containing the 4 UMAP/PCA/LDA functions. Both `song_alchemy.py` and `app_map.py` import from it. | New file + `tasks/song_alchemy.py` + `app_map.py` | 🟡 Medium | Breaks map→alchemy→voyager→mediaserver chain; eliminates the lazy try/except import pattern |
101+
102+
---
103+
104+
## 📋 Summary of Root Causes
105+
106+
| Root Cause | Files Affected | Severity |
107+
|------------|---------------|----------|
108+
| **Module-level import of all 5 media server providers** | `tasks/mediaserver/__init__.py` | 🔴 High — 5 HTTP session setups, 80+ function imports, only 1 provider active |
109+
| **Blueprint imports the heavy task module directly** | `app_alchemy.py``song_alchemy`, `app_map.py``song_alchemy`, `app_voyager.py``voyager_manager`, `app_sonic_fingerprint.py``sonic_fingerprint_manager` | 🟠 Medium — web layer shouldn't pull ML inference dependencies |
110+
| **`from app import app` used instead of `from flask_app import app`** | `app_setup.py` | 🔴 High — defeats the purpose of the `flask_app` singleton designed to break cycles |
111+
| **11 direct subsystem imports in one file** | `tasks/analysis.py` | 🔴 High — the orchestrator has module-level knowledge of every subsystem |
112+
| **`app_helper.py` handles 5 unrelated responsibilities** | `app_helper.py` (imported by 15+ files) | 🟠 Medium — DB, Redis, task status, song features, map projections, SSRF guard all in one file |
113+
| **40+ config constants imported at module top** | `tasks/clustering.py`, `app_clustering.py`, `app_cron.py` | 🟡 Low — noisy but config has zero internal deps so cost is minimal |
114+
115+
---
116+
117+
## ✅ Strengths (What's Already Good)
118+
119+
- **No circular imports** — Clean architecture with `flask_app.py` breaking the potential `app``tasks` cycle
120+
- **Lazy imports used strategically**`app_helper.py`, `tasks/ai/tool_impl.py`, and `tasks/song_alchemy.py` defer heavy imports to function level
121+
- **Config is read-only**`config.py` never imports from business logic; safe to import anywhere
122+
- **Error handling centralized** — All errors flow through `error/error_manager.py``error/error_dictionary.py`
123+
- **Media server abstraction** — Single dispatcher (`tasks/mediaserver/__init__.py`) prevents vendor lock-in, though it needs lazy loading
124+
- **`flask_app` singleton** — Already exists to break import cycles; just needs to be used consistently
125+
- **Provider migration isolated**`tasks/provider_probe.py` deliberately never reads `config.py` globals; takes credentials as parameters
126+
- **MCP tools use lazy imports**`tasks/ai/tool_impl.py` imports heavy subsystems (voyager, artist_gmm, song_alchemy) at function level only

0 commit comments

Comments
 (0)