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

Update api_executor.py

Browse files
Files changed (1) hide show
  1. api_executor.py +83 -65
api_executor.py CHANGED
@@ -1,76 +1,94 @@
1
  """
2
  Flare – API Executor
3
  ~~~~~~~~~~~~~~~~~~~~
4
- header/body templating
5
- auth token caching + refresh
6
- retry w/ tenacity
7
  """
8
 
 
 
9
  import json
10
  import re
11
  import time
12
- from typing import Dict, Any
13
-
14
- import httpx
15
- from tenacity import retry, stop_after_attempt, wait_fixed, wait_exponential
16
 
17
- from config_provider import APIConfig
18
  from utils import log
19
-
20
- _TOKEN_CACHE: Dict[str, Dict[str, Any]] = {} # name -> {token, expiry}
21
-
22
-
23
- def _apply_template(template: Dict[str, str],
24
- variables: Dict[str, str]) -> Dict[str, str]:
25
- out = {}
26
- for k, v in template.items():
27
- out[k] = re.sub(r"{{\s*([a-zA-Z0-9_.]+)\s*}}",
28
- lambda m: variables.get(m.group(1), ""), v)
29
- return out
30
-
31
-
32
- def _get_auth_token(name: str, api_cfg: APIConfig) -> str:
33
- if not api_cfg.auth.enabled:
34
- return ""
35
-
36
- cached = _TOKEN_CACHE.get(name)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  if cached and cached["expiry"] > time.time():
38
- return cached["token"]
39
-
40
- log(f"🔑 Fetching token for API [{name}] ...")
41
- body = _apply_template(api_cfg.auth.body_template, {})
42
- resp = httpx.post(api_cfg.auth.token_endpoint, json=body, timeout=10)
43
- resp.raise_for_status()
44
- token = resp.json()
45
- for segment in api_cfg.auth.response_token_path.split("."):
46
- token = token.get(segment)
47
- _TOKEN_CACHE[name] = {"token": token, "expiry": time.time() + 3000}
48
- return token
49
-
50
-
51
- def _tenacity_wait(cfg: APIConfig):
52
- return (wait_exponential(multiplier=cfg.retry.backoff_seconds)
53
- if cfg.retry.strategy == "exponential"
54
- else wait_fixed(cfg.retry.backoff_seconds))
55
-
56
-
57
- @retry(stop=stop_after_attempt(lambda cfg: cfg.retry.max_attempts),
58
- wait=lambda retry_state: _tenacity_wait(retry_state.args[0]))
59
- def call_api(cfg: APIConfig,
60
- variables: Dict[str, str]) -> httpx.Response:
61
- headers = _apply_template(cfg.headers, {"auth_tokens": _TOKEN_CACHE,
62
- **variables})
63
- body = _apply_template(cfg.body_template, {"variables": variables})
64
-
65
- if cfg.auth.enabled:
66
- headers["Authorization"] = f"Bearer {_get_auth_token(cfg.url, cfg)}"
67
-
68
- log(f"🌐 Calling API {cfg.method} {cfg.url}")
69
- client_args = {"timeout": cfg.timeout_seconds}
70
- if cfg.proxy.enabled:
71
- client_args["proxies"] = {"all://": cfg.proxy.url}
72
-
73
- with httpx.Client(**client_args) as client:
74
- resp = client.request(cfg.method, cfg.url, json=body, headers=headers)
75
- resp.raise_for_status()
76
- return resp
 
1
  """
2
  Flare – API Executor
3
  ~~~~~~~~~~~~~~~~~~~~
4
+ Placeholder yerleştirme ({{variables.x}}, {{auth_tokens.api.token}}, {{config.xxx}})
5
+ Proxy string veya ProxyConfig objesi
6
+ Auth: enabled==True → token alma & cache
7
  """
8
 
9
+ from __future__ import annotations
10
+
11
  import json
12
  import re
13
  import time
14
+ from typing import Any, Dict
 
 
 
15
 
16
+ import requests
17
  from utils import log
18
+ from config_provider import ConfigProvider, APIConfig, ProxyConfig
19
+
20
+ cfg = ConfigProvider.get()
21
+ _token_cache: Dict[str, Dict[str, Any]] = {} # api_name → {token, expiry}
22
+
23
+
24
+ # ----------- Placeholder helpers ----------
25
+ _placeholder_re = re.compile(r"\{\{\s*([^\}]+?)\s*\}\}")
26
+
27
+
28
+ def _render(obj: Any, variables: Dict[str, Any], api_name: str) -> Any:
29
+ def repl(match):
30
+ key = match.group(1)
31
+ if key.startswith("variables."):
32
+ return str(variables.get(key.split(".", 1)[1], ""))
33
+ if key.startswith("auth_tokens."):
34
+ _, api, _ = key.split(".")
35
+ return _token_cache.get(api, {}).get("token", "")
36
+ if key.startswith("config."):
37
+ _, prop = key.split(".", 1)
38
+ return str(getattr(cfg.global_config, prop, ""))
39
+ return match.group(0)
40
+
41
+ if isinstance(obj, str):
42
+ return _placeholder_re.sub(repl, obj)
43
+ if isinstance(obj, dict):
44
+ return {k: _render(v, variables, api_name) for k, v in obj.items()}
45
+ if isinstance(obj, list):
46
+ return [_render(v, variables, api_name) for v in obj]
47
+ return obj
48
+
49
+
50
+ # ----------- Auth helpers -----------------
51
+ def _ensure_token(api: APIConfig):
52
+ if not api.auth or not api.auth.enabled:
53
+ return
54
+ cached = _token_cache.get(api.name)
55
  if cached and cached["expiry"] > time.time():
56
+ return
57
+
58
+ body = api.auth.token_request_body
59
+ body = _render(body, {}, api.name)
60
+ log(f"🔒 Fetching token for {api.name} …")
61
+ r = requests.post(api.auth.token_endpoint, json=body, timeout=api.timeout_seconds)
62
+ r.raise_for_status()
63
+ js = r.json()
64
+ token = js
65
+ for part in api.auth.response_token_path.split("."):
66
+ token = token[part]
67
+ _token_cache[api.name] = {"token": token, "expiry": time.time() + 3500}
68
+
69
+
70
+ # ----------- Main call --------------------
71
+ def call_api(api: APIConfig, variables: Dict[str, Any]):
72
+ _ensure_token(api)
73
+
74
+ headers = _render(api.headers, variables, api.name)
75
+ body = _render(api.body_template, variables, api.name)
76
+ url = api.url
77
+ method = api.method.upper()
78
+
79
+ proxy = None
80
+ if api.proxy:
81
+ proxy = api.proxy if isinstance(api.proxy, str) else (api.proxy.url if api.proxy.enabled else None)
82
+
83
+ log(f"🌐 {api.name} → {method} {url}")
84
+ r = requests.request(
85
+ method,
86
+ url,
87
+ json=body if method in ("POST", "PUT", "PATCH") else None,
88
+ params=body if method == "GET" else None,
89
+ headers=headers,
90
+ proxies={"http": proxy, "https": proxy} if proxy else None,
91
+ timeout=api.timeout_seconds,
92
+ )
93
+ r.raise_for_status()
94
+ return r