sync: 153 file da Baida98/AI@aea39689 (2026-08-15 10:11 UTC) [deploy-all]

#31
by Baida07 - opened
models/ai_client.py CHANGED
@@ -96,6 +96,23 @@ class AIClient:
96
  return os.getenv(definition["model_env"], database_model)
97
  return database_model
98
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  def _try_load_from_supabase(self) -> list[ProviderConfig]:
100
  url = os.getenv("SUPABASE_URL", "")
101
  key = os.getenv("SUPABASE_SERVICE_ROLE_KEY", "")
@@ -104,14 +121,22 @@ class AIClient:
104
  try:
105
  from supabase import create_client
106
  sb = create_client(url, key)
107
- res = (
108
- sb.table("ai_providers")
109
- .select("id,name,api_key,base_url,default_model,tier,purpose")
110
- .eq("is_active", True)
111
- .order("tier", desc=False)
112
- .order("success_count", desc=True)
113
- .execute()
114
- )
 
 
 
 
 
 
 
 
115
  rows = res.data or []
116
  return [
117
  ProviderConfig(
 
96
  return os.getenv(definition["model_env"], database_model)
97
  return database_model
98
 
99
+ @staticmethod
100
+ def _is_legacy_schema_error(exc: Exception) -> bool:
101
+ """Riconosce il layout `ai_providers` precedente alla flotta canonica.
102
+
103
+ Quel layout espone `model_name`, `priority` e `provider_type`, ma
104
+ contiene record storici e modelli deprecati. Fino alla migrazione non va
105
+ promosso a source of truth: il fallback ambiente aggiornato è più sicuro.
106
+ """
107
+ message = str(exc).lower()
108
+ return (
109
+ "column ai_providers." in message
110
+ and "does not exist" in message
111
+ and any(column in message for column in (
112
+ "default_model", "tier", "purpose", "success_count",
113
+ ))
114
+ )
115
+
116
  def _try_load_from_supabase(self) -> list[ProviderConfig]:
117
  url = os.getenv("SUPABASE_URL", "")
118
  key = os.getenv("SUPABASE_SERVICE_ROLE_KEY", "")
 
121
  try:
122
  from supabase import create_client
123
  sb = create_client(url, key)
124
+ try:
125
+ res = (
126
+ sb.table("ai_providers")
127
+ .select("id,name,api_key,base_url,default_model,tier,purpose")
128
+ .eq("is_active", True)
129
+ .order("tier", desc=False)
130
+ .order("success_count", desc=True)
131
+ .execute()
132
+ )
133
+ except Exception as exc:
134
+ if self._is_legacy_schema_error(exc):
135
+ # Non usare il layout storico: contiene provider fittizi e
136
+ # modelli superati. La migrazione normalizzerà la tabella;
137
+ # nel frattempo il caller seleziona il fallback env corrente.
138
+ return []
139
+ raise
140
  rows = res.data or []
141
  return [
142
  ProviderConfig(
tests/test_ai_client_schema_compatibility.py ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import types
4
+ import unittest
5
+ from unittest.mock import patch
6
+
7
+ from models.ai_client import AIClient
8
+
9
+
10
+ class _LegacySchemaError(Exception):
11
+ pass
12
+
13
+
14
+ class _Result:
15
+ def __init__(self, data):
16
+ self.data = data
17
+
18
+
19
+ class _LegacyQuery:
20
+ def __init__(self, rows):
21
+ self.rows = rows
22
+ self.selects = []
23
+
24
+ def select(self, columns):
25
+ self.selects.append(columns)
26
+ if "default_model" in columns:
27
+ raise _LegacySchemaError("column ai_providers.default_model does not exist")
28
+ return self
29
+
30
+ def eq(self, *_args, **_kwargs):
31
+ return self
32
+
33
+ def order(self, *_args, **_kwargs):
34
+ return self
35
+
36
+ def execute(self):
37
+ return _Result(self.rows)
38
+
39
+
40
+ class _LegacySupabase:
41
+ def __init__(self, rows):
42
+ self.query = _LegacyQuery(rows)
43
+
44
+ def table(self, name):
45
+ assert name == "ai_providers"
46
+ return self.query
47
+
48
+
49
+ class LegacySchemaCompatibilityTests(unittest.TestCase):
50
+ def test_detects_only_known_legacy_missing_columns(self):
51
+ self.assertTrue(
52
+ AIClient._is_legacy_schema_error(
53
+ _LegacySchemaError("column ai_providers.default_model does not exist")
54
+ )
55
+ )
56
+ self.assertFalse(
57
+ AIClient._is_legacy_schema_error(
58
+ _LegacySchemaError("column ai_providers.api_key does not exist")
59
+ )
60
+ )
61
+
62
+ def test_legacy_schema_returns_empty_so_current_environment_fallback_stays_authoritative(self):
63
+ legacy_supabase = _LegacySupabase([])
64
+ fake_supabase = types.SimpleNamespace(
65
+ create_client=lambda _url, _key: legacy_supabase,
66
+ )
67
+ client = AIClient.__new__(AIClient)
68
+
69
+ with patch.dict(
70
+ os.environ,
71
+ {"SUPABASE_URL": "https://example.supabase.co", "SUPABASE_SERVICE_ROLE_KEY": "test"},
72
+ clear=True,
73
+ ), patch.dict(sys.modules, {"supabase": fake_supabase}):
74
+ providers = client._try_load_from_supabase()
75
+
76
+ self.assertEqual(legacy_supabase.query.selects, [
77
+ "id,name,api_key,base_url,default_model,tier,purpose",
78
+ ])
79
+ self.assertEqual(providers, [])
80
+
81
+
82
+ if __name__ == "__main__":
83
+ unittest.main()