Skip to content

Commit d7ff540

Browse files
authored
Merge pull request #640 from NeptuneHub/devel
Code refactoring: Import depth cleaning
2 parents 2dce40f + c9d924d commit d7ff540

70 files changed

Lines changed: 3353 additions & 2465 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build-windows.yml

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -141,14 +141,18 @@ jobs:
141141
- name: Create zip for release
142142
run: |
143143
python -c "
144-
import zipfile, pathlib
145-
src = pathlib.Path('artifacts/AudioMuse-AI')
144+
import zipfile, pathlib, sys
145+
# upload-artifact strips the directory pointed at by 'path:', so the
146+
# bundle's files land directly under artifacts/ (not artifacts/AudioMuse-AI/).
147+
src = pathlib.Path('artifacts')
146148
out = 'AudioMuse-AI-amd64-windows.zip'
149+
files = [f for f in sorted(src.rglob('*')) if f.is_file()]
150+
if not files:
151+
sys.exit('No files found under artifacts/ -- the build artifact is missing or empty; refusing to ship an empty zip')
147152
with zipfile.ZipFile(out, 'w', zipfile.ZIP_DEFLATED, allowZip64=True) as zf:
148-
for f in sorted(src.rglob('*')):
149-
if f.is_file():
150-
zf.write(f, f.relative_to(src.parent).as_posix())
151-
print(f'Created {out}')
153+
for f in files:
154+
zf.write(f, 'AudioMuse-AI/' + f.relative_to(src).as_posix())
155+
print(f'Created {out} with {len(files)} files')
152156
"
153157
154158
- name: Attach packages to the release

.github/workflows/lint-flake8.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,4 @@ jobs:
2828
2929
- name: Run flake8 static analysis
3030
run: |
31-
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
31+
flake8 . --count --select=E9,F63,F7,F82,F401,F811 --show-source --statistics

app.py

Lines changed: 58 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,13 @@
3232

3333
# Import helper functions
3434
from app_helper import (
35-
init_db, get_db, close_db,
35+
get_db, close_db,
3636
redis_conn,
3737
get_task_info_from_db,
3838
cancel_job_and_children_recursive,
39+
)
40+
from database import init_db
41+
from config import (
3942
TASK_STATUS_PENDING, TASK_STATUS_STARTED, TASK_STATUS_PROGRESS,
4043
TASK_STATUS_SUCCESS, TASK_STATUS_FAILURE, TASK_STATUS_REVOKED
4144
)
@@ -46,8 +49,6 @@
4649
resolve_jwt_secret,
4750
)
4851

49-
from app_provider_migration import migration_bp
50-
5152
from error import error_manager
5253
from error.error_manager import AudioMuseError
5354
from error.error_dictionary import UNKNOWN_ERROR_CODE
@@ -172,7 +173,7 @@ def teardown_db(e=None):
172173
else:
173174
app.logger.info("RQ worker mode: skipping startup database schema bootstrap.")
174175

175-
import app_setup
176+
import app_setup # noqa: F401
176177

177178
# --- API Endpoints ---
178179

@@ -705,7 +706,7 @@ def listen_for_index_reloads():
705706
load_voyager_index_for_querying(force_reload=True)
706707
from tasks.artist_gmm_manager import load_artist_index_for_querying
707708
load_artist_index_for_querying(force_reload=True)
708-
from app_helper import load_map_projection, load_artist_projection
709+
from database import load_map_projection, load_artist_projection
709710
load_map_projection('main_map', force_reload=True)
710711
load_artist_projection('artist_map', force_reload=True)
711712
# Rebuild the map JSON cache used by the /api/map endpoint
@@ -757,48 +758,56 @@ def listen_for_index_reloads():
757758

758759

759760

760-
# --- Import and Register Blueprints ---
761-
# This is the original, working structure.
762-
from app_chat import chat_bp
763-
from app_clustering import clustering_bp
764-
from app_analysis import analysis_bp
765-
from app_cron import cron_bp, run_due_cron_jobs
766-
from app_voyager import voyager_bp
767-
from app_sonic_fingerprint import sonic_fingerprint_bp
768-
from app_path import path_bp
769-
from app_external import external_bp # --- NEW: Import the external blueprint ---
770-
from app_alchemy import alchemy_bp
771-
from app_map import map_bp
772-
from app_waveform import waveform_bp
773-
from app_artist_similarity import artist_similarity_bp
774-
from app_clap_search import clap_search_bp
775-
from app_lyrics import lyrics_search_bp
776-
from app_sem_grove import sem_grove_bp
777-
from app_backup import backup_bp
778-
from app_dashboard import dashboard_bp
779-
from app_users import users_bp
780-
from app_sync import sync_bp
781-
782-
app.register_blueprint(chat_bp, url_prefix='/chat')
783-
app.register_blueprint(clustering_bp)
784-
app.register_blueprint(analysis_bp)
785-
app.register_blueprint(cron_bp)
786-
app.register_blueprint(voyager_bp)
787-
app.register_blueprint(sonic_fingerprint_bp)
788-
app.register_blueprint(path_bp)
789-
app.register_blueprint(external_bp, url_prefix='/external') # --- NEW: Register the external blueprint ---
790-
app.register_blueprint(alchemy_bp)
791-
app.register_blueprint(map_bp)
792-
app.register_blueprint(waveform_bp)
793-
app.register_blueprint(artist_similarity_bp)
794-
app.register_blueprint(clap_search_bp)
795-
app.register_blueprint(lyrics_search_bp)
796-
app.register_blueprint(sem_grove_bp)
797-
app.register_blueprint(backup_bp)
798-
app.register_blueprint(migration_bp)
799-
app.register_blueprint(dashboard_bp)
800-
app.register_blueprint(users_bp)
801-
app.register_blueprint(sync_bp)
761+
# --- Blueprint Registration ---
762+
# Standard Flask factory pattern: blueprint imports are inside
763+
# this function so the eager import graph stays flat.
764+
765+
766+
def _register_blueprints(flask_app):
767+
from app_chat import chat_bp
768+
from app_clustering import clustering_bp
769+
from app_analysis import analysis_bp
770+
from app_cron import cron_bp
771+
from app_voyager import voyager_bp
772+
from app_sonic_fingerprint import sonic_fingerprint_bp
773+
from app_path import path_bp
774+
from app_external import external_bp
775+
from app_alchemy import alchemy_bp
776+
from app_map import map_bp
777+
from app_waveform import waveform_bp
778+
from app_artist_similarity import artist_similarity_bp
779+
from app_clap_search import clap_search_bp
780+
from app_lyrics import lyrics_search_bp
781+
from app_sem_grove import sem_grove_bp
782+
from app_backup import backup_bp
783+
from app_provider_migration import migration_bp
784+
from app_dashboard import dashboard_bp
785+
from app_users import users_bp
786+
from app_sync import sync_bp
787+
788+
flask_app.register_blueprint(chat_bp, url_prefix='/chat')
789+
flask_app.register_blueprint(clustering_bp)
790+
flask_app.register_blueprint(analysis_bp)
791+
flask_app.register_blueprint(cron_bp)
792+
flask_app.register_blueprint(voyager_bp)
793+
flask_app.register_blueprint(sonic_fingerprint_bp)
794+
flask_app.register_blueprint(path_bp)
795+
flask_app.register_blueprint(external_bp, url_prefix='/external')
796+
flask_app.register_blueprint(alchemy_bp)
797+
flask_app.register_blueprint(map_bp)
798+
flask_app.register_blueprint(waveform_bp)
799+
flask_app.register_blueprint(artist_similarity_bp)
800+
flask_app.register_blueprint(clap_search_bp)
801+
flask_app.register_blueprint(lyrics_search_bp)
802+
flask_app.register_blueprint(sem_grove_bp)
803+
flask_app.register_blueprint(backup_bp)
804+
flask_app.register_blueprint(migration_bp)
805+
flask_app.register_blueprint(dashboard_bp)
806+
flask_app.register_blueprint(users_bp)
807+
flask_app.register_blueprint(sync_bp)
808+
809+
810+
_register_blueprints(app)
802811

803812
# --- Startup: Load indexes and caches (Flask server only, NOT RQ workers) ---
804813
# RQ workers import app.py but should NOT load indexes or start background threads.
@@ -828,7 +837,7 @@ def listen_for_index_reloads():
828837
logger.debug(f"No precomputed map projection to load at startup or load failed: {e}")
829838
# Also try to load artist component projection into memory
830839
try:
831-
from app_helper import load_artist_projection
840+
from database import load_artist_projection
832841
load_artist_projection('artist_map')
833842
logger.info("In-memory artist component projection loaded at startup.")
834843
except Exception as e:
@@ -895,6 +904,7 @@ def _start_map_init_background():
895904
def _cron_manager_loop():
896905
try:
897906
from time import sleep
907+
from app_cron import run_due_cron_jobs
898908
while True:
899909
try:
900910
with app.app_context():

app_alchemy.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ def list_anchors():
185185
500:
186186
description: Database error.
187187
"""
188-
from app_helper import get_alchemy_anchors
188+
from database import get_alchemy_anchors
189189
try:
190190
anchors = get_alchemy_anchors()
191191
# no centroid returned here (name-only list)
@@ -227,7 +227,7 @@ def create_anchor():
227227
500:
228228
description: Database failure.
229229
"""
230-
from app_helper import save_alchemy_anchor
230+
from database import save_alchemy_anchor
231231
payload = request.get_json() or {}
232232
name = (payload.get('name') or '').strip()
233233
centroid = payload.get('centroid')
@@ -260,7 +260,7 @@ def remove_anchor(anchor_id):
260260
404:
261261
description: Anchor not found.
262262
"""
263-
from app_helper import delete_alchemy_anchor
263+
from database import delete_alchemy_anchor
264264
ok = delete_alchemy_anchor(anchor_id)
265265
if not ok:
266266
return jsonify({'error': 'Anchor not found'}), 404
@@ -298,7 +298,7 @@ def rename_anchor(anchor_id):
298298
404:
299299
description: Anchor not found.
300300
"""
301-
from app_helper import update_alchemy_anchor_name
301+
from database import update_alchemy_anchor_name
302302
payload = request.get_json() or {}
303303
name = (payload.get('name') or '').strip()
304304
if not name:
@@ -371,7 +371,7 @@ def list_radios():
371371
500:
372372
description: Database error.
373373
"""
374-
from app_helper import get_alchemy_radios
374+
from database import get_alchemy_radios
375375
try:
376376
radios = get_alchemy_radios()
377377
return jsonify({'radios': [{
@@ -418,7 +418,7 @@ def create_radio():
418418
500:
419419
description: Database failure.
420420
"""
421-
from app_helper import create_alchemy_radio
421+
from database import create_alchemy_radio
422422
payload = request.get_json() or {}
423423
anchor_id = payload.get('anchor_id')
424424
try:
@@ -471,7 +471,7 @@ def update_radio(radio_id):
471471
404:
472472
description: Radio not found.
473473
"""
474-
from app_helper import update_alchemy_radio
474+
from database import update_alchemy_radio
475475
payload = request.get_json() or {}
476476
temperature, n_results, error = _parse_radio_settings(payload)
477477
if error:
@@ -502,7 +502,7 @@ def remove_radio(radio_id):
502502
404:
503503
description: Radio not found.
504504
"""
505-
from app_helper import delete_alchemy_radio
505+
from database import delete_alchemy_radio
506506
ok = delete_alchemy_radio(radio_id)
507507
if not ok:
508508
return jsonify({'error': 'Radio not found'}), 404
@@ -588,7 +588,7 @@ def artist_projections_api():
588588
500:
589589
description: Failure to read cache.
590590
"""
591-
from app_helper import ARTIST_PROJECTION_CACHE
591+
from database import ARTIST_PROJECTION_CACHE
592592

593593
try:
594594
if not ARTIST_PROJECTION_CACHE:

app_analysis.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
# app_analysis.py
2-
from flask import Blueprint, jsonify, request
2+
from flask import Blueprint, jsonify, request, render_template
33
import uuid
44
import logging
55

66
# Import configuration from the main config.py
7-
from config import NUM_RECENT_ALBUMS, TOP_N_MOODS
7+
from config import NUM_RECENT_ALBUMS, TOP_N_MOODS, TASK_STATUS_PENDING
88

99
# RQ import
1010
from rq import Retry
1111

12+
# App helper functions
13+
from app_helper import rq_queue_high, save_task_status
14+
from database import clean_up_previous_main_tasks, get_active_main_task
15+
1216
logger = logging.getLogger(__name__)
1317

1418
# Create a Blueprint for analysis-related routes
@@ -29,7 +33,6 @@ def cleaning_page():
2933
schema:
3034
type: string
3135
"""
32-
from flask import render_template
3336
return render_template('cleaning.html', title = 'AudioMuse-AI - Database Cleaning', active='cleaning')
3437

3538
@analysis_bp.route('/api/analysis/start', methods=['POST'])
@@ -80,9 +83,6 @@ def start_analysis_endpoint():
8083
500:
8184
description: Server error during task enqueue.
8285
"""
83-
# Local imports to prevent circular dependency at startup
84-
from app_helper import rq_queue_high, clean_up_previous_main_tasks, save_task_status, TASK_STATUS_PENDING, get_active_main_task
85-
8686
# Check for any existing active main task to prevent parallel batch runs.
8787
active_task = get_active_main_task()
8888
if active_task:
@@ -146,9 +146,6 @@ def start_cleaning_endpoint():
146146
500:
147147
description: Server error during task enqueue.
148148
"""
149-
# Local imports to prevent circular dependency at startup
150-
from app_helper import rq_queue_high, clean_up_previous_main_tasks, save_task_status, TASK_STATUS_PENDING, get_active_main_task
151-
152149
active_task = get_active_main_task()
153150
if active_task:
154151
return jsonify({

app_clustering.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,32 @@
55
import traceback
66

77
# Import all necessary configuration variables
8-
from config import MAX_SONGS_PER_CLUSTER, SCORE_WEIGHT_DIVERSITY, SCORE_WEIGHT_SILHOUETTE, SCORE_WEIGHT_DAVIES_BOULDIN, SCORE_WEIGHT_CALINSKI_HARABASZ, SCORE_WEIGHT_PURITY, SCORE_WEIGHT_OTHER_FEATURE_DIVERSITY, SCORE_WEIGHT_OTHER_FEATURE_PURITY, MIN_SONGS_PER_GENRE_FOR_STRATIFICATION, STRATIFIED_SAMPLING_TARGET_PERCENTILE, CLUSTER_ALGORITHM, NUM_CLUSTERS_MIN, NUM_CLUSTERS_MAX, DBSCAN_EPS_MIN, DBSCAN_EPS_MAX, DBSCAN_MIN_SAMPLES_MIN, DBSCAN_MIN_SAMPLES_MAX, GMM_N_COMPONENTS_MIN, GMM_N_COMPONENTS_MAX, SPECTRAL_N_CLUSTERS_MIN, SPECTRAL_N_CLUSTERS_MAX, ENABLE_CLUSTERING_EMBEDDINGS, PCA_COMPONENTS_MIN, PCA_COMPONENTS_MAX, CLUSTERING_RUNS, TOP_N_MOODS, AI_MODEL_PROVIDER, OLLAMA_SERVER_URL, OLLAMA_MODEL_NAME, OPENAI_SERVER_URL, OPENAI_MODEL_NAME, OPENAI_API_KEY, GEMINI_API_KEY, GEMINI_MODEL_NAME, TOP_N_PLAYLISTS, MISTRAL_API_KEY, MISTRAL_MODEL_NAME
8+
from config import (
9+
MAX_SONGS_PER_CLUSTER, SCORE_WEIGHT_DIVERSITY, SCORE_WEIGHT_SILHOUETTE,
10+
SCORE_WEIGHT_DAVIES_BOULDIN, SCORE_WEIGHT_CALINSKI_HARABASZ,
11+
SCORE_WEIGHT_PURITY, SCORE_WEIGHT_OTHER_FEATURE_DIVERSITY,
12+
SCORE_WEIGHT_OTHER_FEATURE_PURITY, MIN_SONGS_PER_GENRE_FOR_STRATIFICATION,
13+
STRATIFIED_SAMPLING_TARGET_PERCENTILE, CLUSTER_ALGORITHM, NUM_CLUSTERS_MIN,
14+
NUM_CLUSTERS_MAX, DBSCAN_EPS_MIN, DBSCAN_EPS_MAX, DBSCAN_MIN_SAMPLES_MIN,
15+
DBSCAN_MIN_SAMPLES_MAX, GMM_N_COMPONENTS_MIN, GMM_N_COMPONENTS_MAX,
16+
SPECTRAL_N_CLUSTERS_MIN, SPECTRAL_N_CLUSTERS_MAX, ENABLE_CLUSTERING_EMBEDDINGS,
17+
PCA_COMPONENTS_MIN, PCA_COMPONENTS_MAX, CLUSTERING_RUNS, TOP_N_MOODS,
18+
AI_MODEL_PROVIDER, OLLAMA_SERVER_URL, OLLAMA_MODEL_NAME, OPENAI_SERVER_URL,
19+
OPENAI_MODEL_NAME, OPENAI_API_KEY, GEMINI_API_KEY, GEMINI_MODEL_NAME,
20+
TOP_N_PLAYLISTS, MISTRAL_API_KEY, MISTRAL_MODEL_NAME,
21+
TASK_STATUS_PENDING, TASK_STATUS_FAILURE,
22+
)
923

1024
# RQ import
1125
from rq import Retry
1226

1327
from error import error_manager
1428
from error.error_dictionary import ERR_CLUSTERING_FAILED
1529

30+
# App helper functions
31+
from app_helper import rq_queue_high, save_task_status
32+
from database import clean_up_previous_main_tasks, get_active_main_task
33+
1634

1735
logger = logging.getLogger(__name__)
1836

@@ -22,7 +40,6 @@
2240
def clustering_task_failure_handler(job, connection, type, value, tb):
2341
"""A failure handler for the main clustering task, executed by the worker."""
2442
from flask_app import app
25-
from app_helper import save_task_status, TASK_STATUS_FAILURE
2643
with app.app_context():
2744
task_id = getattr(job, 'id', None) or getattr(job, 'get_id', lambda: None)()
2845

@@ -245,10 +262,6 @@ def start_clustering_endpoint():
245262
status:
246263
type: string
247264
"""
248-
# Local imports to prevent circular dependency at startup
249-
from app_helper import rq_queue_high, get_active_main_task
250-
from app_helper import clean_up_previous_main_tasks, save_task_status, TASK_STATUS_PENDING
251-
252265
# Check for any existing active main task to prevent parallel batch runs
253266
active_task = get_active_main_task()
254267
if active_task:

app_cron.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
from flask import Blueprint, render_template, jsonify, request
22
from psycopg2.extras import DictCursor
3-
from database import get_db
3+
from database import get_db, save_task_status
44
from taskqueue import rq_queue_high
5-
from app_helper import save_task_status, TASK_STATUS_PENDING
5+
from config import TASK_STATUS_PENDING
66
import uuid, time, logging
77
from config import (
88
TOP_N_MOODS,
@@ -240,8 +240,6 @@ def run_due_cron_jobs():
240240
"pca_components_max": int(PCA_COMPONENTS_MAX),
241241
"num_clustering_runs": int(CLUSTERING_RUNS),
242242
"max_songs_per_cluster_val": int(MAX_SONGS_PER_CLUSTER),
243-
"gmm_n_components_min": int(GMM_N_COMPONENTS_MIN),
244-
"gmm_n_components_max": int(GMM_N_COMPONENTS_MAX),
245243
"top_n_playlists_param": int(TOP_N_PLAYLISTS),
246244
"min_songs_per_genre_for_stratification_param": int(MIN_SONGS_PER_GENRE_FOR_STRATIFICATION),
247245
"stratified_sampling_target_percentile_param": int(STRATIFIED_SAMPLING_TARGET_PERCENTILE),

0 commit comments

Comments
 (0)