Skip to content

Commit 7333005

Browse files
committed
improve async client
1 parent 88a8490 commit 7333005

2 files changed

Lines changed: 61 additions & 97 deletions

File tree

Lines changed: 8 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,29 @@
11
import httpx
22

33
async def get(self, endpoint, *args, **kwargs):
4-
"""Async version of GET request"""
54
await self.validate_session_async()
65
auth = (self.email, self.password) if self.auth else None
76

8-
async with httpx.AsyncClient(timeout=self.timeout) as client:
9-
res = await client.get(
10-
self.domain + endpoint,
11-
headers=self.header,
12-
auth=auth,
13-
**kwargs
14-
)
15-
if 'raw' in args:
16-
return res
17-
else:
18-
return res.json() if res.status_code == 200 else False
7+
res = await self._client.get(endpoint, auth=auth, **kwargs)
8+
return res if "raw" in args else (res.json() if res.status_code == 200 else False)
199

2010
async def post(self, endpoint, *args, **kwargs):
21-
"""Async version of POST request"""
2211
await self.validate_session_async()
2312
auth = (self.email, self.password) if self.auth else None
2413

25-
async with httpx.AsyncClient(timeout=self.timeout) as client:
26-
res = await client.post(
27-
self.domain + endpoint,
28-
headers=self.header,
29-
auth=auth,
30-
**kwargs
31-
)
32-
if 'raw' in args:
33-
return res
34-
else:
35-
return res.json() if res.status_code == 200 else False
14+
res = await self._client.post(endpoint, auth=auth, **kwargs)
15+
return res if "raw" in args else (res.json() if res.status_code == 200 else False)
3616

3717
async def put(self, endpoint, *args, **kwargs):
38-
"""Async version of PUT request for updating objects"""
3918
await self.validate_session_async()
4019
auth = (self.email, self.password) if self.auth else None
4120

42-
async with httpx.AsyncClient(timeout=self.timeout) as client:
43-
res = await client.put(
44-
self.domain + endpoint,
45-
headers=self.header,
46-
auth=auth,
47-
**kwargs
48-
)
49-
if 'raw' in args:
50-
return res
51-
else:
52-
return res.status_code
21+
res = await self._client.put(endpoint, auth=auth, **kwargs)
22+
return res if "raw" in args else res.status_code
5323

5424
async def delete(self, endpoint, *args, **kwargs):
55-
"""Async version of DELETE request"""
5625
await self.validate_session_async()
5726
auth = (self.email, self.password) if self.auth else None
5827

59-
async with httpx.AsyncClient(timeout=self.timeout) as client:
60-
res = await client.delete(
61-
self.domain + endpoint,
62-
headers=self.header,
63-
auth=auth,
64-
**kwargs
65-
)
66-
if 'raw' in args:
67-
return res
68-
else:
69-
return res.status_code
28+
res = await self._client.delete(endpoint, auth=auth, **kwargs)
29+
return res if "raw" in args else res.status_code

metabase_api/metabase_api_async.py

Lines changed: 53 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,10 @@
22
import getpass
33

44
class Metabase_API_Async:
5-
"""
6-
Async version of the Metabase API wrapper.
7-
Provides asynchronous methods to interact with the Metabase API.
8-
"""
9-
10-
def __init__(self, domain, email=None, password=None, api_key=None, basic_auth=False, is_admin=True, timeout=None):
5+
def __init__(self,domain,email=None,password=None,api_key=None,basic_auth=False,is_admin=True,timeout=None,
6+
*,trust_env=False,http2=False,limits=None,verify=True):
117
assert email is not None or api_key is not None
12-
self.domain = domain.rstrip('/')
8+
self.domain = domain.rstrip("/")
139
self.email = email
1410
self.auth = None
1511
self.password = None
@@ -19,62 +15,70 @@ def __init__(self, domain, email=None, password=None, api_key=None, basic_auth=F
1915
self.timeout = timeout
2016

2117
if email:
22-
self.password = getpass.getpass(prompt='Please enter your password: ') if password is None else password
23-
if basic_auth:
24-
self.auth = True # We'll use aiohttp.BasicAuth in the request methods
25-
else:
26-
self.auth = None
18+
self.password = getpass.getpass(prompt="Please enter your password: ") if password is None else password
19+
self.auth = True if basic_auth else None
2720
else:
2821
self.header = {"X-API-KEY": api_key}
29-
22+
23+
# Connection pooling and keep-alive
24+
self._limits = limits or httpx.Limits(max_connections=20, max_keepalive_connections=20)
25+
self._client = httpx.AsyncClient(
26+
base_url=self.domain,
27+
timeout=self.timeout,
28+
trust_env=trust_env,
29+
http2=http2,
30+
limits=self._limits,
31+
verify=verify,
32+
headers=self.header, # default headers; can be updated after auth
33+
)
34+
3035
if not self.is_admin:
31-
print('''
32-
Ask your Metabase admin to disable "Friendly Table and Field Names" (in Admin Panel > Settings > General).
33-
Without this some of the functions of the current package may not work as expected.
34-
''')
35-
36-
async def authenticate_async(self):
37-
"""Asynchronously get a Session ID"""
38-
conn_header = {
39-
'username': self.email,
40-
'password': self.password
41-
}
36+
print(
37+
"""
38+
Ask your Metabase admin to disable "Friendly Table and Field Names" (in Admin Panel > Settings > General).
39+
Without this some of the functions of the current package may not work as expected.
40+
"""
41+
)
42+
43+
async def aclose(self):
44+
await self._client.aclose()
45+
46+
async def __aenter__(self):
47+
return self
4248

49+
async def __aexit__(self, exc_type, exc, tb):
50+
await self.aclose()
51+
52+
async def authenticate_async(self):
53+
conn_header = {"username": self.email, "password": self.password}
4354
auth = (self.email, self.password) if self.auth else None
44-
async with httpx.AsyncClient() as client:
45-
res = await client.post(
46-
self.domain + '/api/session',
47-
json=conn_header,
48-
auth=auth
49-
)
50-
if res.status_code != 200:
51-
raise Exception(f"Authentication failed with status {res.status_code}")
5255

53-
data = res.json()
54-
self.session_id = data['id']
55-
self.header = {'X-Metabase-Session': self.session_id}
56+
res = await self._client.post("/api/session", json=conn_header, auth=auth)
57+
if res.status_code != 200:
58+
raise Exception(f"Authentication failed with status {res.status_code}")
59+
60+
self.session_id = res.json()["id"]
61+
self.header = {"X-Metabase-Session": self.session_id}
62+
63+
# Update default headers used by the pooled client
64+
self._client.headers.clear()
65+
self._client.headers.update(self.header)
5666

5767
async def validate_session_async(self):
58-
"""Asynchronously get a new session ID if the previous one has expired"""
5968
if not self.email: # Using API key
6069
return
6170

62-
if not self.session_id: # First request
71+
if not self.session_id:
6372
return await self.authenticate_async()
6473

6574
auth = (self.email, self.password) if self.auth else None
66-
async with httpx.AsyncClient() as client:
67-
res = await client.get(
68-
self.domain + '/api/user/current',
69-
headers=self.header,
70-
auth=auth
71-
)
72-
if res.status_code == 200:
73-
return True
74-
elif res.status_code == 401: # unauthorized
75-
return await self.authenticate_async()
76-
else:
77-
raise Exception(f"Session validation failed with status {res.status_code}")
75+
res = await self._client.get("/api/user/current", auth=auth)
76+
77+
if res.status_code == 200:
78+
return True
79+
if res.status_code == 401:
80+
return await self.authenticate_async()
81+
raise Exception(f"Session validation failed with status {res.status_code}")
7882

7983

8084

0 commit comments

Comments
 (0)