ciyidogan commited on
Commit
3a3a88f
·
verified ·
1 Parent(s): b482671

Update config_provider.py

Browse files
Files changed (1) hide show
  1. config_provider.py +132 -84
config_provider.py CHANGED
@@ -1,166 +1,214 @@
1
  """
2
- Flare – ConfigProvider (JSONC-safe)
3
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
- Pydantic v2 (pattern=…)
5
- Yorum ayıklayıcı string literal’leri korur
 
 
6
  """
7
 
8
  from __future__ import annotations
9
 
10
  import json
11
  from pathlib import Path
12
- from typing import Dict, List, Optional
13
 
14
  from pydantic import BaseModel, Field, HttpUrl, ValidationError
15
  from utils import log
16
 
17
- # -------------------- Model tanımları --------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  class RetryConfig(BaseModel):
19
- strategy: str = Field("static", pattern=r"^(static|exponential)$")
20
- retry_count: int = 3
21
  backoff_seconds: int = 2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
 
 
23
 
 
 
24
  class ParameterConfig(BaseModel):
25
  name: str
26
- type: str = Field(..., pattern=r"^(int|float|str|bool)$")
 
27
  required: bool = True
 
 
28
  invalid_prompt: Optional[str] = None
 
 
 
 
29
 
30
 
31
  class IntentConfig(BaseModel):
32
  name: str
33
- action: str
 
 
 
 
34
  parameters: List[ParameterConfig] = []
 
 
35
  fallback_error_prompt: Optional[str] = None
36
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
  class VersionConfig(BaseModel):
39
- id: int
40
- caption: str
 
41
  general_prompt: str
42
- is_published: bool = Field(False, alias="published")
43
- intents: List[IntentConfig] = []
 
 
 
44
 
45
 
46
  class ProjectConfig(BaseModel):
 
47
  name: str
48
- caption: str
 
 
49
  versions: List[VersionConfig]
50
 
 
 
51
 
52
- class APIAuthConfig(BaseModel):
53
- token_endpoint: HttpUrl
54
- refresh_endpoint: Optional[HttpUrl] = None
55
- client_id: str
56
- client_secret: str
57
 
 
 
 
 
 
58
 
59
- class APIConfig(BaseModel):
60
- name: str
61
- url: HttpUrl
62
- method: str = Field("GET", pattern=r"^(GET|POST|PUT|PATCH|DELETE)$")
63
- timeout_seconds: int = 10
64
- proxy: Optional[str] = None
65
- auth: Optional[APIAuthConfig] = None
66
- response_prompt: Optional[str] = None
67
- retry: Optional[RetryConfig] = None
68
 
 
 
69
 
70
- class ServiceConfig(BaseModel):
71
- projects: List[ProjectConfig]
72
- apis: Dict[str, APIConfig]
73
- locale: str = "tr-TR"
74
- retry: RetryConfig = RetryConfig()
75
 
76
 
77
- # -------------------- Provider singleton ----------------
78
  class ConfigProvider:
79
- _config: Optional[ServiceConfig] = None
80
  _CONFIG_PATH = Path(__file__).parent / "service_config.jsonc"
81
 
82
  @classmethod
83
  def get(cls) -> ServiceConfig:
84
- if cls._config is None:
85
- cls._config = cls._load_config()
86
- return cls._config
 
87
 
88
- # ---------------- internal ----------------
89
  @classmethod
90
- def _load_config(cls) -> ServiceConfig:
91
  log(f"📥 Loading service config from {cls._CONFIG_PATH.name} …")
92
  raw = cls._CONFIG_PATH.read_text(encoding="utf-8")
93
  json_str = cls._strip_jsonc(raw)
94
  try:
95
  data = json.loads(json_str)
96
  cfg = ServiceConfig.model_validate(data)
97
- log("✅ Service config loaded successfully.")
98
  return cfg
99
  except (json.JSONDecodeError, ValidationError) as exc:
100
  log(f"❌ Config validation error: {exc}")
101
  raise
102
 
103
- # -------- robust JSONC stripper ----------
104
  @staticmethod
105
  def _strip_jsonc(text: str) -> str:
106
- """Remove // line comments and /* block comments */ without touching string literals."""
107
- OUT, IN_STR, ESC, IN_SLASH, IN_BLOCK = 0, 1, 2, 3, 4
108
- state = OUT
109
- res = []
110
-
111
- i = 0
112
  while i < len(text):
113
  ch = text[i]
114
-
115
  if state == OUT:
116
  if ch == '"':
117
- state = IN_STR
118
- res.append(ch)
119
  elif ch == '/':
120
- # Could be // or /* – peek next char
121
  nxt = text[i + 1] if i + 1 < len(text) else ""
122
  if nxt == '/':
123
- state = IN_SLASH
124
- i += 1 # skip nxt in loop
125
  elif nxt == '*':
126
- state = IN_BLOCK
127
- i += 1
128
  else:
129
  res.append(ch)
130
  else:
131
  res.append(ch)
132
-
133
- elif state == IN_STR:
134
  res.append(ch)
135
- if ch == '\\':
136
- state = ESC # escape next
137
- elif ch == '"':
138
- state = OUT
139
-
140
  elif state == ESC:
141
  res.append(ch)
142
- state = IN_STR
143
-
144
- elif state == IN_SLASH:
145
  if ch == '\n':
146
- res.append(ch) # keep newline
147
  state = OUT
148
-
149
- elif state == IN_BLOCK:
150
  if ch == '*' and i + 1 < len(text) and text[i + 1] == '/':
151
- i += 1 # skip /
152
- state = OUT
153
-
154
  i += 1
155
-
156
  return ''.join(res)
157
-
158
-
159
- # --------------- exports ---------------
160
- RetryConfig = RetryConfig
161
- ParameterConfig = ParameterConfig
162
- IntentConfig = IntentConfig
163
- VersionConfig = VersionConfig
164
- APIConfig = APIConfig
165
- ProjectConfig = ProjectConfig
166
- ServiceConfig = ServiceConfig
 
1
  """
2
+ Flare – Configuration Loader (JSONC + Pydantic v2)
3
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
+ Global, Project, Version, Intent, API tanımları
5
+ JSONC yorum temizleme (string literal’lere dokunmaz)
6
+ • Alias’lar (version_number, max_attempts vb.)
7
+ • apis[] artık liste → runtime’da dict’e map’lenir
8
  """
9
 
10
  from __future__ import annotations
11
 
12
  import json
13
  from pathlib import Path
14
+ from typing import Any, Dict, List, Optional
15
 
16
  from pydantic import BaseModel, Field, HttpUrl, ValidationError
17
  from utils import log
18
 
19
+ # ---------------- Global -----------------
20
+ class UserConfig(BaseModel):
21
+ username: str
22
+ password_hash: str
23
+ salt: str
24
+
25
+
26
+ class GlobalConfig(BaseModel):
27
+ work_mode: str = Field("hfcloud", pattern=r"^(hfcloud|cloud|on-premise)$")
28
+ cloud_token: Optional[str] = None
29
+ spark_endpoint: HttpUrl
30
+ users: List[UserConfig] = []
31
+
32
+
33
+ # ---------------- Retry / Proxy ----------
34
  class RetryConfig(BaseModel):
35
+ retry_count: int = Field(3, alias="max_attempts")
 
36
  backoff_seconds: int = 2
37
+ strategy: str = Field("static", pattern=r"^(static|exponential)$")
38
+
39
+
40
+ class ProxyConfig(BaseModel):
41
+ enabled: bool = True
42
+ url: HttpUrl
43
+
44
+
45
+ # ---------------- API & Auth -------------
46
+ class APIAuthConfig(BaseModel):
47
+ enabled: bool = False
48
+ token_endpoint: Optional[HttpUrl] = None
49
+ response_token_path: str = "access_token"
50
+ token_request_body: Dict[str, Any] = Field({}, alias="body_template")
51
+ token_refresh_endpoint: Optional[HttpUrl] = None
52
+ token_refresh_body: Dict[str, Any] = {}
53
+
54
+ class Config:
55
+ extra = "allow"
56
+
57
+
58
+ class APIConfig(BaseModel):
59
+ name: str
60
+ url: HttpUrl
61
+ method: str = Field("GET", pattern=r"^(GET|POST|PUT|PATCH|DELETE)$")
62
+ headers: Dict[str, Any] = {}
63
+ body_template: Dict[str, Any] = {}
64
+ timeout_seconds: int = 10
65
+ retry: RetryConfig = RetryConfig()
66
+ proxy: Optional[str | ProxyConfig] = None
67
+ auth: Optional[APIAuthConfig] = None
68
+ response_prompt: Optional[str] = None
69
 
70
+ class Config:
71
+ extra = "allow"
72
 
73
+
74
+ # ---------------- Intent / Param ---------
75
  class ParameterConfig(BaseModel):
76
  name: str
77
+ caption: Optional[str] = ""
78
+ type: str = Field(..., pattern=r"^(int|float|bool|str|string)$")
79
  required: bool = True
80
+ extraction_prompt: Optional[str] = None
81
+ validation_regex: Optional[str] = None
82
  invalid_prompt: Optional[str] = None
83
+ type_error_prompt: Optional[str] = None
84
+
85
+ def canonical_type(self) -> str:
86
+ return "str" if self.type == "string" else self.type
87
 
88
 
89
  class IntentConfig(BaseModel):
90
  name: str
91
+ caption: Optional[str] = ""
92
+ locale: str = "tr-TR"
93
+ dependencies: List[str] = []
94
+ examples: List[str] = []
95
+ detection_prompt: Optional[str] = None
96
  parameters: List[ParameterConfig] = []
97
+ action: str
98
+ fallback_timeout_prompt: Optional[str] = None
99
  fallback_error_prompt: Optional[str] = None
100
 
101
+ class Config:
102
+ extra = "allow"
103
+
104
+
105
+ # ---------------- Version / Project ------
106
+ class LLMConfig(BaseModel):
107
+ repo_id: str
108
+ generation_config: Dict[str, Any] = {}
109
+ use_fine_tune: bool = False
110
+ fine_tune_zip: str = ""
111
+
112
 
113
  class VersionConfig(BaseModel):
114
+ id: int = Field(..., alias="version_number")
115
+ caption: Optional[str] = ""
116
+ published: bool = False
117
  general_prompt: str
118
+ llm: LLMConfig
119
+ intents: List[IntentConfig]
120
+
121
+ class Config:
122
+ extra = "allow"
123
 
124
 
125
  class ProjectConfig(BaseModel):
126
+ id: Optional[int] = None
127
  name: str
128
+ caption: Optional[str] = ""
129
+ enabled: bool = True
130
+ last_version_number: Optional[int] = None
131
  versions: List[VersionConfig]
132
 
133
+ class Config:
134
+ extra = "allow"
135
 
 
 
 
 
 
136
 
137
+ # ---------------- Service Config ---------
138
+ class ServiceConfig(BaseModel):
139
+ global_config: GlobalConfig = Field(..., alias="config")
140
+ projects: List[ProjectConfig]
141
+ apis: List[APIConfig]
142
 
143
+ # runtime helpers (skip validation)
144
+ _api_by_name: Dict[str, APIConfig] = {}
 
 
 
 
 
 
 
145
 
146
+ def build_index(self):
147
+ self._api_by_name = {a.name: a for a in self.apis}
148
 
149
+ def get_api(self, name: str) -> APIConfig | None:
150
+ return self._api_by_name.get(name)
 
 
 
151
 
152
 
153
+ # ---------------- Provider Singleton -----
154
  class ConfigProvider:
155
+ _instance: Optional[ServiceConfig] = None
156
  _CONFIG_PATH = Path(__file__).parent / "service_config.jsonc"
157
 
158
  @classmethod
159
  def get(cls) -> ServiceConfig:
160
+ if cls._instance is None:
161
+ cls._instance = cls._load()
162
+ cls._instance.build_index()
163
+ return cls._instance
164
 
165
+ # -------- Internal helpers ------------
166
  @classmethod
167
+ def _load(cls) -> ServiceConfig:
168
  log(f"📥 Loading service config from {cls._CONFIG_PATH.name} …")
169
  raw = cls._CONFIG_PATH.read_text(encoding="utf-8")
170
  json_str = cls._strip_jsonc(raw)
171
  try:
172
  data = json.loads(json_str)
173
  cfg = ServiceConfig.model_validate(data)
174
+ log("✅ Service config loaded.")
175
  return cfg
176
  except (json.JSONDecodeError, ValidationError) as exc:
177
  log(f"❌ Config validation error: {exc}")
178
  raise
179
 
 
180
  @staticmethod
181
  def _strip_jsonc(text: str) -> str:
182
+ """Remove // and /* */ comments (string-aware)."""
183
+ OUT, STR, ESC, SLASH, BLOCK = 0, 1, 2, 3, 4
184
+ state, res, i = OUT, [], 0
 
 
 
185
  while i < len(text):
186
  ch = text[i]
 
187
  if state == OUT:
188
  if ch == '"':
189
+ state, res = STR, res + [ch]
 
190
  elif ch == '/':
 
191
  nxt = text[i + 1] if i + 1 < len(text) else ""
192
  if nxt == '/':
193
+ state, i = SLASH, i + 1
 
194
  elif nxt == '*':
195
+ state, i = BLOCK, i + 1
 
196
  else:
197
  res.append(ch)
198
  else:
199
  res.append(ch)
200
+ elif state == STR:
 
201
  res.append(ch)
202
+ state = ESC if ch == '\\' else (OUT if ch == '"' else STR)
 
 
 
 
203
  elif state == ESC:
204
  res.append(ch)
205
+ state = STR
206
+ elif state == SLASH:
 
207
  if ch == '\n':
208
+ res.append(ch)
209
  state = OUT
210
+ elif state == BLOCK:
 
211
  if ch == '*' and i + 1 < len(text) and text[i + 1] == '/':
212
+ i, state = i + 1, OUT
 
 
213
  i += 1
 
214
  return ''.join(res)