ciyidogan commited on
Commit
8dbff89
Β·
verified Β·
1 Parent(s): 6d55afc

Update api_executor.py

Browse files
Files changed (1) hide show
  1. api_executor.py +209 -67
api_executor.py CHANGED
@@ -1,86 +1,228 @@
1
  """
2
- Flare – API Executor (token refresh eklenmiş)
3
  """
4
 
5
  from __future__ import annotations
6
  import json, re, time, requests
7
- from typing import Any, Dict
8
  from utils import log
9
- from config_provider import ConfigProvider, APIConfig, ProxyConfig
10
- cfg = ConfigProvider.get()
11
 
12
- _token: Dict[str, Dict[str, Any]] = {} # {api: {token, expiry, refresh_ep, refresh_body}}
13
 
14
  _placeholder = re.compile(r"\{\{\s*([^\}]+?)\s*\}\}")
15
 
16
- def _render(obj, vars, api):
17
- def r(m):
18
- key=m.group(1)
 
19
  if key.startswith("variables."):
20
- return str(vars.get(key.split(".",1)[1],""))
 
21
  if key.startswith("auth_tokens."):
22
- _, apiname, _ = key.split(".")
23
- return _token.get(apiname,{}).get("token","")
 
 
 
 
24
  if key.startswith("config."):
25
- return str(getattr(cfg.global_config, key.split(".",1)[1], ""))
26
- return m.group(0)
27
- if isinstance(obj,str): return _placeholder.sub(r,obj)
28
- if isinstance(obj,dict): return {k:_render(v,vars,api) for k,v in obj.items()}
29
- if isinstance(obj,list): return [_render(v,vars,api) for v in obj]
 
 
 
 
 
30
  return obj
31
 
32
- def _fetch_token(api: APIConfig):
33
- body=_render(api.auth.token_request_body,{},api.name)
34
- r=requests.post(api.auth.token_endpoint,json=body,timeout=api.timeout_seconds)
35
- r.raise_for_status()
36
- js=r.json()
37
- tok=js
38
- for p in api.auth.response_token_path.split("."): tok=tok[p]
39
- _token[api.name]={
40
- "token":tok,
41
- "expiry":time.time()+3500,
42
- "refresh_ep":api.auth.token_refresh_endpoint,
43
- "refresh_body":api.auth.token_refresh_body,
44
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
 
46
- def _ensure_token(api: APIConfig):
47
- if not api.auth or not api.auth.enabled: return
48
- info=_token.get(api.name)
49
- if not info: _fetch_token(api); return
50
- if info["expiry"]>time.time(): return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
- # refresh varsa dene
53
- if info["refresh_ep"]:
54
- body=_render(info["refresh_body"],{},api.name)
55
- try:
56
- r=requests.post(info["refresh_ep"],json=body,timeout=api.timeout_seconds)
57
- r.raise_for_status()
58
- js=r.json()
59
- tok=js
60
- for p in api.auth.response_token_path.split("."): tok=tok[p]
61
- info["token"]=tok; info["expiry"]=time.time()+3500
62
- return
63
- except Exception as e:
64
- log(f"⚠️ token refresh fail {e}")
 
 
 
 
 
 
 
 
 
65
 
66
- _fetch_token(api)
67
-
68
- def call_api(api: APIConfig, vars: Dict[str,Any]):
69
- _ensure_token(api)
70
- hdr=_render(api.headers,vars,api.name)
71
- body=_render(api.body_template,vars,api.name)
72
- proxy=None
 
 
 
 
73
  if api.proxy:
74
- proxy=api.proxy if isinstance(api.proxy,str) else (api.proxy.url if api.proxy.enabled else None)
75
- log(f"🌐 {api.name} {api.method} {api.url}")
76
- r=requests.request(
77
- api.method,
78
- api.url,
79
- json=body if api.method in ("POST","PUT","PATCH") else None,
80
- params=body if api.method=="GET" else None,
81
- headers=hdr,
82
- proxies={"http":proxy,"https":proxy} if proxy else None,
83
- timeout=api.timeout_seconds,
84
- )
85
- r.raise_for_status()
86
- return r
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  """
2
+ Flare – API Executor (v2.0 Β· session-aware token management)
3
  """
4
 
5
  from __future__ import annotations
6
  import json, re, time, requests
7
+ from typing import Any, Dict, Optional
8
  from utils import log
9
+ from config_provider import ConfigProvider, APIConfig
10
+ from session import Session
11
 
12
+ cfg = ConfigProvider.get()
13
 
14
  _placeholder = re.compile(r"\{\{\s*([^\}]+?)\s*\}\}")
15
 
16
+ def _render(obj: Any, session: Session, api_name: str) -> Any:
17
+ """Render template with session variables and tokens"""
18
+ def replacer(match):
19
+ key = match.group(1)
20
  if key.startswith("variables."):
21
+ var_name = key.split(".", 1)[1]
22
+ return str(session.variables.get(var_name, ""))
23
  if key.startswith("auth_tokens."):
24
+ parts = key.split(".")
25
+ if len(parts) >= 3:
26
+ token_api = parts[1]
27
+ token_field = parts[2]
28
+ token_data = session.auth_tokens.get(token_api, {})
29
+ return str(token_data.get(token_field, ""))
30
  if key.startswith("config."):
31
+ attr_name = key.split(".", 1)[1]
32
+ return str(getattr(cfg.global_config, attr_name, ""))
33
+ return match.group(0)
34
+
35
+ if isinstance(obj, str):
36
+ return _placeholder.sub(replacer, obj)
37
+ if isinstance(obj, dict):
38
+ return {k: _render(v, session, api_name) for k, v in obj.items()}
39
+ if isinstance(obj, list):
40
+ return [_render(v, session, api_name) for v in obj]
41
  return obj
42
 
43
+ def _fetch_token(api: APIConfig, session: Session) -> None:
44
+ """Fetch new auth token"""
45
+ if not api.auth or not api.auth.enabled:
46
+ return
47
+
48
+ log(f"πŸ”‘ Fetching token for {api.name}")
49
+
50
+ try:
51
+ body = _render(api.auth.token_request_body, session, api.name)
52
+ headers = {"Content-Type": "application/json"}
53
+
54
+ response = requests.post(
55
+ str(api.auth.token_endpoint),
56
+ json=body,
57
+ headers=headers,
58
+ timeout=api.timeout_seconds
59
+ )
60
+ response.raise_for_status()
61
+
62
+ json_data = response.json()
63
+
64
+ # Extract token using path
65
+ token = json_data
66
+ for path_part in api.auth.response_token_path.split("."):
67
+ token = token.get(path_part)
68
+ if token is None:
69
+ raise ValueError(f"Token path {api.auth.response_token_path} not found in response")
70
+
71
+ # Store in session
72
+ session.auth_tokens[api.name] = {
73
+ "token": token,
74
+ "expiry": time.time() + 3500, # ~1 hour
75
+ "refresh_token": json_data.get("refresh_token")
76
+ }
77
+
78
+ log(f"βœ… Token obtained for {api.name}")
79
+
80
+ except Exception as e:
81
+ log(f"❌ Token fetch failed for {api.name}: {e}")
82
+ raise
83
 
84
+ def _refresh_token(api: APIConfig, session: Session) -> bool:
85
+ """Refresh existing token"""
86
+ if not api.auth or not api.auth.token_refresh_endpoint:
87
+ return False
88
+
89
+ token_info = session.auth_tokens.get(api.name, {})
90
+ if not token_info.get("refresh_token"):
91
+ return False
92
+
93
+ log(f"πŸ”„ Refreshing token for {api.name}")
94
+
95
+ try:
96
+ body = _render(api.auth.token_refresh_body or {}, session, api.name)
97
+ body["refresh_token"] = token_info["refresh_token"]
98
+
99
+ response = requests.post(
100
+ str(api.auth.token_refresh_endpoint),
101
+ json=body,
102
+ timeout=api.timeout_seconds
103
+ )
104
+ response.raise_for_status()
105
+
106
+ json_data = response.json()
107
+
108
+ # Extract new token
109
+ token = json_data
110
+ for path_part in api.auth.response_token_path.split("."):
111
+ token = token.get(path_part)
112
+ if token is None:
113
+ raise ValueError(f"Token path {api.auth.response_token_path} not found in refresh response")
114
+
115
+ # Update session
116
+ session.auth_tokens[api.name] = {
117
+ "token": token,
118
+ "expiry": time.time() + 3500,
119
+ "refresh_token": json_data.get("refresh_token", token_info["refresh_token"])
120
+ }
121
+
122
+ log(f"βœ… Token refreshed for {api.name}")
123
+ return True
124
+
125
+ except Exception as e:
126
+ log(f"❌ Token refresh failed for {api.name}: {e}")
127
+ return False
128
 
129
+ def _ensure_token(api: APIConfig, session: Session) -> None:
130
+ """Ensure valid token exists for API"""
131
+ if not api.auth or not api.auth.enabled:
132
+ return
133
+
134
+ token_info = session.auth_tokens.get(api.name)
135
+
136
+ # No token yet
137
+ if not token_info:
138
+ _fetch_token(api, session)
139
+ return
140
+
141
+ # Token still valid
142
+ if token_info.get("expiry", 0) > time.time():
143
+ return
144
+
145
+ # Try refresh first
146
+ if _refresh_token(api, session):
147
+ return
148
+
149
+ # Refresh failed, get new token
150
+ _fetch_token(api, session)
151
 
152
+ def call_api(api: APIConfig, session: Session) -> requests.Response:
153
+ """Execute API call with automatic token management"""
154
+ # Ensure valid token
155
+ _ensure_token(api, session)
156
+
157
+ # Prepare request
158
+ headers = _render(api.headers, session, api.name)
159
+ body = _render(api.body_template, session, api.name)
160
+
161
+ # Handle proxy
162
+ proxies = None
163
  if api.proxy:
164
+ if isinstance(api.proxy, str):
165
+ proxies = {"http": api.proxy, "https": api.proxy}
166
+ elif hasattr(api.proxy, "enabled") and api.proxy.enabled:
167
+ proxy_url = str(api.proxy.url)
168
+ proxies = {"http": proxy_url, "https": proxy_url}
169
+
170
+ # Prepare request parameters
171
+ request_params = {
172
+ "method": api.method,
173
+ "url": str(api.url),
174
+ "headers": headers,
175
+ "timeout": api.timeout_seconds
176
+ }
177
+
178
+ # Add body based on method
179
+ if api.method in ("POST", "PUT", "PATCH"):
180
+ request_params["json"] = body
181
+ elif api.method == "GET" and body:
182
+ request_params["params"] = body
183
+
184
+ if proxies:
185
+ request_params["proxies"] = proxies
186
+
187
+ # Execute with retry
188
+ retry_count = api.retry.retry_count if api.retry else 0
189
+ last_error = None
190
+
191
+ for attempt in range(retry_count + 1):
192
+ try:
193
+ log(f"🌐 API call: {api.name} {api.method} {api.url} (attempt {attempt + 1}/{retry_count + 1})")
194
+
195
+ response = requests.request(**request_params)
196
+
197
+ # Handle 401 Unauthorized
198
+ if response.status_code == 401 and api.auth and api.auth.enabled and attempt < retry_count:
199
+ log(f"πŸ”’ Got 401, refreshing token for {api.name}")
200
+ _fetch_token(api, session) # Force new token
201
+ headers = _render(api.headers, session, api.name) # Re-render headers
202
+ request_params["headers"] = headers
203
+ continue
204
+
205
+ response.raise_for_status()
206
+ log(f"βœ… API call successful: {api.name} ({response.status_code})")
207
+ return response
208
+
209
+ except requests.exceptions.Timeout as e:
210
+ last_error = e
211
+ log(f"⏱️ API timeout for {api.name} (attempt {attempt + 1})")
212
+
213
+ except requests.exceptions.RequestException as e:
214
+ last_error = e
215
+ log(f"❌ API error for {api.name}: {e}")
216
+
217
+ # Retry backoff
218
+ if attempt < retry_count:
219
+ backoff = api.retry.backoff_seconds if api.retry else 2
220
+ if api.retry and api.retry.strategy == "exponential":
221
+ backoff = backoff * (2 ** attempt)
222
+ log(f"⏳ Waiting {backoff}s before retry...")
223
+ time.sleep(backoff)
224
+
225
+ # All retries failed
226
+ if last_error:
227
+ raise last_error
228
+ raise requests.exceptions.RequestException(f"API call failed after {retry_count + 1} attempts")