From 654edb5e5e5d83ae107f1ada54a63cecc18e2260 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 17 Aug 2026 16:10:32 +0000 Subject: [PATCH] chore(lint): weekly black/isort/flake8 sweep Auto-generated by the QueryGrade weekly lint routine. Tooling: black + isort across analyzer/ and querygrade/. --- .../commands/process_ml_feedback.py | 8 +--- .../management/commands/train_ml_model.py | 4 +- analyzer/migrations/0008_mlmodelartifact.py | 40 ++++++++++++++----- analyzer/ml/core/hybrid_grader.py | 4 +- analyzer/ml/core/training_gates.py | 4 +- analyzer/ml/core/training_pipeline.py | 8 +--- analyzer/ml/tests/test_alert_notifier.py | 3 +- analyzer/ml/tests/test_model_storage.py | 4 +- .../tests/test_process_ml_feedback_command.py | 4 +- analyzer/ml/tests/test_training_gates.py | 4 +- analyzer/test_analytics.py | 10 +++-- analyzer/test_seo.py | 4 +- analyzer/urls.py | 2 +- analyzer/views/ml_alert_views.py | 7 +++- querygrade/settings.py | 8 +--- 15 files changed, 58 insertions(+), 56 deletions(-) diff --git a/analyzer/management/commands/process_ml_feedback.py b/analyzer/management/commands/process_ml_feedback.py index 36b3883..d35ec82 100644 --- a/analyzer/management/commands/process_ml_feedback.py +++ b/analyzer/management/commands/process_ml_feedback.py @@ -184,9 +184,7 @@ def show_feedback_statistics(self, options): self.stdout.write(f" {field:<18} {counts}") # Queries with feedback - queries_with_feedback = ( - histories.values("query").distinct().count() - ) + queries_with_feedback = histories.values("query").distinct().count() total_queries = Query.objects.count() self.stdout.write("") self.stdout.write( @@ -225,9 +223,7 @@ def _get_queries_to_process(self, options): if not options["force_all"]: cutoff_date = timezone.now() - timedelta(days=options["days"]) recent = self._feedback_histories().filter(submitted_at__gte=cutoff_date) - queryset = queryset.filter( - id__in=recent.values("query") - ).distinct() + queryset = queryset.filter(id__in=recent.values("query")).distinct() # Get queries with sufficient feedback queries_to_process = [] diff --git a/analyzer/management/commands/train_ml_model.py b/analyzer/management/commands/train_ml_model.py index 9d0a22e..7538173 100644 --- a/analyzer/management/commands/train_ml_model.py +++ b/analyzer/management/commands/train_ml_model.py @@ -131,9 +131,7 @@ def handle(self, *args, **options): else: # Say why. Silently not deploying is how a bad model # gets mistaken for a deploy that just did not happen. - self.stdout.write( - self.style.WARNING(f"Not deployed: {reason}") - ) + self.stdout.write(self.style.WARNING(f"Not deployed: {reason}")) else: raise CommandError(f"Training failed: {result.error_message}") diff --git a/analyzer/migrations/0008_mlmodelartifact.py b/analyzer/migrations/0008_mlmodelartifact.py index 50cf8ed..ade8fa9 100644 --- a/analyzer/migrations/0008_mlmodelartifact.py +++ b/analyzer/migrations/0008_mlmodelartifact.py @@ -1,26 +1,48 @@ # Generated by Django 4.2.30 on 2026-07-18 05:03 -from django.db import migrations, models import django.db.models.deletion import django.utils.timezone +from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('analyzer', '0007_queryanalysis_schema_insights'), + ("analyzer", "0007_queryanalysis_schema_insights"), ] operations = [ migrations.CreateModel( - name='MLModelArtifact', + name="MLModelArtifact", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('data', models.BinaryField(help_text='joblib-serialized model bytes')), - ('byte_size', models.BigIntegerField(default=0)), - ('checksum', models.CharField(blank=True, help_text='SHA256 of the stored bytes', max_length=64)), - ('created_at', models.DateTimeField(default=django.utils.timezone.now)), - ('model', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='artifact', to='analyzer.mlmodel')), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("data", models.BinaryField(help_text="joblib-serialized model bytes")), + ("byte_size", models.BigIntegerField(default=0)), + ( + "checksum", + models.CharField( + blank=True, + help_text="SHA256 of the stored bytes", + max_length=64, + ), + ), + ("created_at", models.DateTimeField(default=django.utils.timezone.now)), + ( + "model", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + related_name="artifact", + to="analyzer.mlmodel", + ), + ), ], ), ] diff --git a/analyzer/ml/core/hybrid_grader.py b/analyzer/ml/core/hybrid_grader.py index 72a9875..474c5c9 100644 --- a/analyzer/ml/core/hybrid_grader.py +++ b/analyzer/ml/core/hybrid_grader.py @@ -254,9 +254,7 @@ def _load_current_model(self) -> Optional[Any]: model_data = retrieve_artifact(active_model) if model_data is None: - model_file_path = os.path.join( - self.model_path, active_model.file_path - ) + model_file_path = os.path.join(self.model_path, active_model.file_path) if not os.path.exists(model_file_path): logger.error(f"Model file not found: {model_file_path}") return None diff --git a/analyzer/ml/core/training_gates.py b/analyzer/ml/core/training_gates.py index f2df1a3..192809f 100644 --- a/analyzer/ml/core/training_gates.py +++ b/analyzer/ml/core/training_gates.py @@ -22,9 +22,7 @@ def real_training_sample_count() -> int: """Count TrainingData rows that came from real feedback, not the seed.""" from ...models import TrainingData - return TrainingData.objects.exclude( - validation_source=SYNTHETIC_SEED_SOURCE - ).count() + return TrainingData.objects.exclude(validation_source=SYNTHETIC_SEED_SOURCE).count() def real_feedback_gate(): diff --git a/analyzer/ml/core/training_pipeline.py b/analyzer/ml/core/training_pipeline.py index 61e734f..e7b437c 100644 --- a/analyzer/ml/core/training_pipeline.py +++ b/analyzer/ml/core/training_pipeline.py @@ -264,9 +264,7 @@ def run_training_pipeline(self, force_retrain: bool = False) -> TrainingResult: if ok: self._deploy_model(model_version) else: - logger.warning( - f"Model {model_version} not deployed: {reason}" - ) + logger.warning(f"Model {model_version} not deployed: {reason}") training_time = (timezone.now() - start_time).total_seconds() @@ -640,9 +638,7 @@ def cleanup_old_models(self, keep_versions: int = 5): os.remove(local_file) logger.info(f"Removed old model file: {local_file}") except OSError as e: - logger.warning( - f"Could not remove model file {local_file}: {e}" - ) + logger.warning(f"Could not remove model file {local_file}: {e}") # Remove database record (cascades to MLModelArtifact) model.delete() diff --git a/analyzer/ml/tests/test_alert_notifier.py b/analyzer/ml/tests/test_alert_notifier.py index d78d49d..93de971 100644 --- a/analyzer/ml/tests/test_alert_notifier.py +++ b/analyzer/ml/tests/test_alert_notifier.py @@ -189,13 +189,14 @@ def setUp(self): cache.clear() def test_evaluation_sends_one_email_per_new_alert(self): + from django.utils import timezone as dj_timezone + from analyzer.ml.monitoring import alert_evaluator from analyzer.ml.monitoring.retraining_system import ( RetrainingTrigger, TriggerReason, TriggerUrgency, ) - from django.utils import timezone as dj_timezone triggers = [ RetrainingTrigger( diff --git a/analyzer/ml/tests/test_model_storage.py b/analyzer/ml/tests/test_model_storage.py index 7285378..086ddac 100644 --- a/analyzer/ml/tests/test_model_storage.py +++ b/analyzer/ml/tests/test_model_storage.py @@ -97,9 +97,7 @@ def test_load_current_model_uses_db_artifact_without_local_file(self): # came from the database. with tempfile.TemporaryDirectory() as empty_dir: grader.model_path = empty_dir - self.assertFalse( - os.path.exists(os.path.join(empty_dir, row.file_path)) - ) + self.assertFalse(os.path.exists(os.path.join(empty_dir, row.file_path))) loaded = grader._load_current_model() self.assertIsNotNone(loaded) diff --git a/analyzer/ml/tests/test_process_ml_feedback_command.py b/analyzer/ml/tests/test_process_ml_feedback_command.py index 4a24672..985317f 100644 --- a/analyzer/ml/tests/test_process_ml_feedback_command.py +++ b/analyzer/ml/tests/test_process_ml_feedback_command.py @@ -145,9 +145,7 @@ def test_selection_threshold_defaults_to_the_collector_threshold(self): output = self.run_cmd("--stats-only") - self.assertIn( - f"(>= {collector_min} feedback items): 0", output - ) + self.assertIn(f"(>= {collector_min} feedback items): 0", output) def test_query_with_enough_feedback_is_found_and_processed(self): self.add_detailed_feedback(FeedbackCollector().min_feedback_count) diff --git a/analyzer/ml/tests/test_training_gates.py b/analyzer/ml/tests/test_training_gates.py index 7865281..0252437 100644 --- a/analyzer/ml/tests/test_training_gates.py +++ b/analyzer/ml/tests/test_training_gates.py @@ -71,9 +71,7 @@ def test_real_count_ignores_seed_even_when_mixed(self): self.assertTrue(training_gates.real_feedback_gate()[0]) -@override_settings( - ML_MIN_REAL_FEEDBACK_SAMPLES=3, ML_MIN_TRAINING_SAMPLES=1 -) +@override_settings(ML_MIN_REAL_FEEDBACK_SAMPLES=3, ML_MIN_TRAINING_SAMPLES=1) class TrainingPipelineGateTests(TestCase): """The pipeline must refuse synthetic-only data even when the plain sample-count gate (ML_MIN_TRAINING_SAMPLES) would pass.""" diff --git a/analyzer/test_analytics.py b/analyzer/test_analytics.py index f372b85..0be6c82 100644 --- a/analyzer/test_analytics.py +++ b/analyzer/test_analytics.py @@ -133,8 +133,9 @@ def test_login_fires_user_login_on_the_next_page(self): self.assertEqual(response.status_code, 302) self.assertIn("_auth_user_id", self.client.session) - self.assertEqual(self.rendered_event(self.client.get(response["Location"])), - "user_login") + self.assertEqual( + self.rendered_event(self.client.get(response["Location"])), "user_login" + ) def test_logout_survives_the_session_flush(self): """logout() flushes the session, then the view writes the flag into @@ -145,8 +146,9 @@ def test_logout_survives_the_session_flush(self): self.assertEqual(response.status_code, 302) self.assertNotIn("_auth_user_id", self.client.session) - self.assertEqual(self.rendered_event(self.client.get(response["Location"])), - "user_logout") + self.assertEqual( + self.rendered_event(self.client.get(response["Location"])), "user_logout" + ) def test_event_fires_exactly_once(self): """The pop has to mark the session dirty, or the flag survives and diff --git a/analyzer/test_seo.py b/analyzer/test_seo.py index a8fa439..6858b03 100644 --- a/analyzer/test_seo.py +++ b/analyzer/test_seo.py @@ -131,9 +131,7 @@ def test_home_page_has_exactly_one_self_referencing_canonical(self): html = response.content.decode() self.assertEqual(html.count('rel="canonical"'), 1) - self.assertIn( - '', html - ) + self.assertIn('', html) def test_home_page_is_indexable_and_describes_itself(self): response = self.client.get("/") diff --git a/analyzer/urls.py b/analyzer/urls.py index e7a481f..94d59df 100644 --- a/analyzer/urls.py +++ b/analyzer/urls.py @@ -9,7 +9,6 @@ # ML Dashboard views (separate module) from .ml import dashboard_views -from .views import ml_alert_views # Import from modular views package from .views import ( # Authentication views; Query grading views; Comparison views; Batch analysis views; History and feedback views; Upload views; Database introspection views; Async processing views; API views; Saved connection views @@ -39,6 +38,7 @@ index, login_view, logout_view, + ml_alert_views, password_change, password_reset_confirm, password_reset_request, diff --git a/analyzer/views/ml_alert_views.py b/analyzer/views/ml_alert_views.py index 8d916a4..ebcdaf7 100644 --- a/analyzer/views/ml_alert_views.py +++ b/analyzer/views/ml_alert_views.py @@ -20,8 +20,11 @@ from django.utils import timezone from django.views.decorators.http import require_POST -from analyzer.ml.monitoring.rollback import can_rollback -from analyzer.ml.monitoring.rollback import RollbackError, perform_rollback +from analyzer.ml.monitoring.rollback import ( + RollbackError, + can_rollback, + perform_rollback, +) from analyzer.models import MLAlert, MLModel logger = logging.getLogger(__name__) diff --git a/querygrade/settings.py b/querygrade/settings.py index 20c59f6..808cfaf 100644 --- a/querygrade/settings.py +++ b/querygrade/settings.py @@ -140,9 +140,7 @@ # ship another non-predictive model. This gate counts only rows whose # validation_source is not the synthetic seed, so retraining waits for genuine # user feedback to accumulate (see #92). -ML_MIN_REAL_FEEDBACK_SAMPLES = int( - os.environ.get("ML_MIN_REAL_FEEDBACK_SAMPLES", "25") -) +ML_MIN_REAL_FEEDBACK_SAMPLES = int(os.environ.get("ML_MIN_REAL_FEEDBACK_SAMPLES", "25")) # Deploy quality gate, read by TrainingConfig. A model must clear BOTH the # validation and the held-out test bar, and not show too large a gap between @@ -152,9 +150,7 @@ ML_TEST_PERFORMANCE_THRESHOLD = float( os.environ.get("ML_TEST_PERFORMANCE_THRESHOLD", "0.7") ) -ML_MAX_VALIDATION_TEST_GAP = float( - os.environ.get("ML_MAX_VALIDATION_TEST_GAP", "0.15") -) +ML_MAX_VALIDATION_TEST_GAP = float(os.environ.get("ML_MAX_VALIDATION_TEST_GAP", "0.15")) # ML Feature Flags # Default OFF: hybrid grading only fires for authenticated users, and the app