-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapi_client.py
More file actions
130 lines (117 loc) · 5.59 KB
/
api_client.py
File metadata and controls
130 lines (117 loc) · 5.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
"""
Simple API client wrapper that provides GET, POST, PUT, PATCH using
requests when available and falling back to urllib when not.
Each method returns a dictionary with keys:
- ok (bool)
- status_code (int or None)
- headers (dict-like) or {}
- text (raw response text)
- json (parsed JSON or None)
- error (exception string on failure)
Designed to be safe to import and use from `agent.py`.
"""
from typing import Optional, Any, Dict
import json
import ssl
try:
import requests
except Exception:
requests = None
import urllib.request
import urllib.parse
import urllib.error
class APIClient:
def __init__(self, verify: bool = True, default_headers: Optional[Dict[str, str]] = None):
self.verify = verify
self.default_headers = default_headers or {}
def _build_headers(self, headers: Optional[Dict[str, str]]):
h = dict(self.default_headers)
if headers:
h.update(headers)
return h
def get(self, url: str, params: Optional[Dict[str, Any]] = None, headers: Optional[Dict[str, str]] = None,
timeout: int = 10, verify: Optional[bool] = None):
if params:
qs = urllib.parse.urlencode(params)
url = f"{url}?{qs}"
return self._request('GET', url, None, headers, timeout, verify)
def post(self, url: str, data: Optional[Any] = None, json_body: Optional[Any] = None,
headers: Optional[Dict[str, str]] = None, timeout: int = 10, verify: Optional[bool] = None):
payload = json_body if json_body is not None else data
return self._request('POST', url, payload, headers, timeout, verify)
def put(self, url: str, data: Optional[Any] = None, json_body: Optional[Any] = None,
headers: Optional[Dict[str, str]] = None, timeout: int = 10, verify: Optional[bool] = None):
payload = json_body if json_body is not None else data
return self._request('PUT', url, payload, headers, timeout, verify)
def patch(self, url: str, data: Optional[Any] = None, json_body: Optional[Any] = None,
headers: Optional[Dict[str, str]] = None, timeout: int = 10, verify: Optional[bool] = None):
payload = json_body if json_body is not None else data
return self._request('PATCH', url, payload, headers, timeout, verify)
def _request(self, method: str, url: str, payload: Optional[Any], headers: Optional[Dict[str, str]],
timeout: int, verify: Optional[bool]):
hdrs = self._build_headers(headers)
verify_final = self.verify if verify is None else bool(verify)
if requests:
try:
# requests will handle JSON if json= is provided
req_kwargs = dict(method=method, url=url, headers=hdrs, timeout=timeout)
if isinstance(payload, (dict, list)):
req_kwargs['json'] = payload
elif payload is not None:
# send raw payload as data
req_kwargs['data'] = payload
req_kwargs['verify'] = verify_final
r = requests.request(**req_kwargs)
try:
parsed = r.json()
except Exception:
parsed = None
return {
'ok': r.ok,
'status_code': r.status_code,
'headers': dict(r.headers) if r.headers is not None else {},
'text': r.text,
'json': parsed,
'error': None
}
except Exception as e:
return {'ok': False, 'status_code': None, 'headers': {}, 'text': '', 'json': None, 'error': str(e)}
# Fallback: urllib
try:
data_bytes = None
if payload is not None:
if isinstance(payload, (dict, list)):
if 'Content-Type' not in hdrs:
hdrs['Content-Type'] = 'application/json'
data_bytes = json.dumps(payload).encode('utf-8')
elif isinstance(payload, str):
data_bytes = payload.encode('utf-8')
elif isinstance(payload, bytes):
data_bytes = payload
else:
# try to stringify
data_bytes = str(payload).encode('utf-8')
req = urllib.request.Request(url, data=data_bytes, headers=hdrs, method=method)
ctx = None if verify_final else ssl._create_unverified_context()
with urllib.request.urlopen(req, timeout=timeout, context=ctx) as resp:
raw = resp.read()
try:
text = raw.decode('utf-8')
except Exception:
text = raw.decode('latin-1', errors='ignore')
resp_headers = dict(resp.getheaders()) if hasattr(resp, 'getheaders') else {}
parsed = None
try:
parsed = json.loads(text)
except Exception:
parsed = None
return {'ok': True, 'status_code': getattr(resp, 'status', None) or None,
'headers': resp_headers, 'text': text, 'json': parsed, 'error': None}
except urllib.error.HTTPError as he:
try:
body = he.read().decode('utf-8', errors='ignore')
except Exception:
body = ''
return {'ok': False, 'status_code': he.code, 'headers': {}, 'text': body, 'json': None, 'error': str(he)}
except Exception as e:
return {'ok': False, 'status_code': None, 'headers': {}, 'text': '', 'json': None, 'error': str(e)}