Skip to content

Commit 3eebe9e

Browse files
committed
refactor: extract client classes into separate module files without breaking API
1 parent 25ec00e commit 3eebe9e

5 files changed

Lines changed: 204 additions & 183 deletions

File tree

openapi_python_sdk/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
Openapi Python SDK - A minimal and agnostic SDK for the Openapi marketplace.
33
Exports both synchronous and asynchronous clients.
44
"""
5-
from .client import AsyncClient, AsyncOauthClient, Client, OauthClient
5+
from .async_client import AsyncClient
6+
from .async_oauth_client import AsyncOauthClient
7+
from .client import Client
8+
from .oauth_client import OauthClient
69

710
__all__ = ["Client", "AsyncClient", "OauthClient", "AsyncOauthClient"]

openapi_python_sdk/async_client.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import json
2+
from typing import Any, Dict
3+
4+
import httpx
5+
6+
7+
class AsyncClient:
8+
"""
9+
Asynchronous client for making authenticated requests to Openapi endpoints.
10+
Suitable for use with FastAPI, aiohttp, etc.
11+
"""
12+
13+
def __init__(self, token: str):
14+
self.client = httpx.AsyncClient()
15+
self.auth_header: str = f"Bearer {token}"
16+
self.headers: Dict[str, str] = {
17+
"Authorization": self.auth_header,
18+
"Content-Type": "application/json",
19+
}
20+
21+
async def __aenter__(self):
22+
"""Enable use as an asynchronous context manager."""
23+
return self
24+
25+
async def __aexit__(self, exc_type, exc_val, exc_tb):
26+
"""Ensure the underlying HTTP client is closed on exit (async)."""
27+
await self.client.aclose()
28+
29+
async def aclose(self):
30+
"""Manually close the underlying HTTP client (async)."""
31+
await self.client.aclose()
32+
33+
async def request(
34+
self,
35+
method: str = "GET",
36+
url: str = None,
37+
payload: Dict[str, Any] = None,
38+
params: Dict[str, Any] = None,
39+
) -> Dict[str, Any]:
40+
"""
41+
Make an asynchronous HTTP request to the specified Openapi endpoint.
42+
"""
43+
payload = payload or {}
44+
params = params or {}
45+
url = url or ""
46+
resp = await self.client.request(
47+
method=method,
48+
url=url,
49+
headers=self.headers,
50+
json=payload,
51+
params=params,
52+
)
53+
data = resp.json()
54+
55+
# Handle cases where the API might return a JSON-encoded string instead of an object
56+
if isinstance(data, str):
57+
try:
58+
data = json.loads(data)
59+
except json.JSONDecodeError:
60+
pass
61+
62+
return data
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import base64
2+
from typing import Any, Dict, List
3+
4+
import httpx
5+
6+
from .oauth_client import OAUTH_BASE_URL, TEST_OAUTH_BASE_URL
7+
8+
9+
class AsyncOauthClient:
10+
"""
11+
Asynchronous client for handling Openapi authentication and token management.
12+
Suitable for use with FastAPI, aiohttp, etc.
13+
"""
14+
15+
def __init__(self, username: str, apikey: str, test: bool = False):
16+
self.client = httpx.AsyncClient()
17+
self.url: str = TEST_OAUTH_BASE_URL if test else OAUTH_BASE_URL
18+
self.auth_header: str = (
19+
"Basic " + base64.b64encode(f"{username}:{apikey}".encode("utf-8")).decode()
20+
)
21+
self.headers: Dict[str, Any] = {
22+
"Authorization": self.auth_header,
23+
"Content-Type": "application/json",
24+
}
25+
26+
async def __aenter__(self):
27+
"""Enable use as an asynchronous context manager."""
28+
return self
29+
30+
async def __aexit__(self, exc_type, exc_val, exc_tb):
31+
"""Ensure the underlying HTTP client is closed on exit (async)."""
32+
await self.client.aclose()
33+
34+
async def aclose(self):
35+
"""Manually close the underlying HTTP client (async)."""
36+
await self.client.aclose()
37+
38+
async def get_scopes(self, limit: bool = False) -> Dict[str, Any]:
39+
"""Retrieve available scopes for the current user (async)."""
40+
params = {"limit": int(limit)}
41+
url = f"{self.url}/scopes"
42+
resp = await self.client.get(url=url, headers=self.headers, params=params)
43+
return resp.json()
44+
45+
async def create_token(self, scopes: List[str] = [], ttl: int = 0) -> Dict[str, Any]:
46+
"""Create a new bearer token with specified scopes and TTL (async)."""
47+
payload = {"scopes": scopes, "ttl": ttl}
48+
url = f"{self.url}/token"
49+
resp = await self.client.post(url=url, headers=self.headers, json=payload)
50+
return resp.json()
51+
52+
async def get_token(self, scope: str = None) -> Dict[str, Any]:
53+
"""Retrieve an existing token, optionally filtered by scope (async)."""
54+
params = {"scope": scope or ""}
55+
url = f"{self.url}/token"
56+
resp = await self.client.get(url=url, headers=self.headers, params=params)
57+
return resp.json()
58+
59+
async def delete_token(self, id: str) -> Dict[str, Any]:
60+
"""Revoke/Delete a specific token by ID (async)."""
61+
url = f"{self.url}/token/{id}"
62+
resp = await self.client.delete(url=url, headers=self.headers)
63+
return resp.json()
64+
65+
async def get_counters(self, period: str, date: str) -> Dict[str, Any]:
66+
"""Retrieve usage counters for a specific period and date (async)."""
67+
url = f"{self.url}/counters/{period}/{date}"
68+
resp = await self.client.get(url=url, headers=self.headers)
69+
return resp.json()

openapi_python_sdk/client.py

Lines changed: 5 additions & 182 deletions
Original file line numberDiff line numberDiff line change
@@ -1,131 +1,12 @@
1-
import base64
21
import json
3-
from typing import Any, Dict, List, Optional
2+
from typing import Any, Dict
43

54
import httpx
65

7-
OAUTH_BASE_URL = "https://oauth.openapi.it"
8-
TEST_OAUTH_BASE_URL = "https://test.oauth.openapi.it"
9-
10-
11-
class OauthClient:
12-
"""
13-
Synchronous client for handling Openapi authentication and token management.
14-
"""
15-
16-
def __init__(self, username: str, apikey: str, test: bool = False):
17-
self.client = httpx.Client()
18-
self.url: str = TEST_OAUTH_BASE_URL if test else OAUTH_BASE_URL
19-
self.auth_header: str = (
20-
"Basic " + base64.b64encode(f"{username}:{apikey}".encode("utf-8")).decode()
21-
)
22-
self.headers: Dict[str, Any] = {
23-
"Authorization": self.auth_header,
24-
"Content-Type": "application/json",
25-
}
26-
27-
def __enter__(self):
28-
"""Enable use as a synchronous context manager."""
29-
return self
30-
31-
def __exit__(self, exc_type, exc_val, exc_tb):
32-
"""Ensure the underlying HTTP client is closed on exit."""
33-
self.client.close()
34-
35-
def close(self):
36-
"""Manually close the underlying HTTP client."""
37-
self.client.close()
38-
39-
def get_scopes(self, limit: bool = False) -> Dict[str, Any]:
40-
"""Retrieve available scopes for the current user."""
41-
params = {"limit": int(limit)}
42-
url = f"{self.url}/scopes"
43-
return self.client.get(url=url, headers=self.headers, params=params).json()
44-
45-
def create_token(self, scopes: List[str] = [], ttl: int = 0) -> Dict[str, Any]:
46-
"""Create a new bearer token with specified scopes and TTL."""
47-
payload = {"scopes": scopes, "ttl": ttl}
48-
url = f"{self.url}/token"
49-
return self.client.post(url=url, headers=self.headers, json=payload).json()
50-
51-
def get_token(self, scope: str = None) -> Dict[str, Any]:
52-
"""Retrieve an existing token, optionally filtered by scope."""
53-
params = {"scope": scope or ""}
54-
url = f"{self.url}/token"
55-
return self.client.get(url=url, headers=self.headers, params=params).json()
56-
57-
def delete_token(self, id: str) -> Dict[str, Any]:
58-
"""Revoke/Delete a specific token by ID."""
59-
url = f"{self.url}/token/{id}"
60-
return self.client.delete(url=url, headers=self.headers).json()
61-
62-
def get_counters(self, period: str, date: str) -> Dict[str, Any]:
63-
"""Retrieve usage counters for a specific period and date."""
64-
url = f"{self.url}/counters/{period}/{date}"
65-
return self.client.get(url=url, headers=self.headers).json()
66-
67-
68-
class AsyncOauthClient:
69-
"""
70-
Asynchronous client for handling Openapi authentication and token management.
71-
Suitable for use with FastAPI, aiohttp, etc.
72-
"""
73-
74-
def __init__(self, username: str, apikey: str, test: bool = False):
75-
self.client = httpx.AsyncClient()
76-
self.url: str = TEST_OAUTH_BASE_URL if test else OAUTH_BASE_URL
77-
self.auth_header: str = (
78-
"Basic " + base64.b64encode(f"{username}:{apikey}".encode("utf-8")).decode()
79-
)
80-
self.headers: Dict[str, Any] = {
81-
"Authorization": self.auth_header,
82-
"Content-Type": "application/json",
83-
}
84-
85-
async def __aenter__(self):
86-
"""Enable use as an asynchronous context manager."""
87-
return self
88-
89-
async def __aexit__(self, exc_type, exc_val, exc_tb):
90-
"""Ensure the underlying HTTP client is closed on exit (async)."""
91-
await self.client.aclose()
92-
93-
async def aclose(self):
94-
"""Manually close the underlying HTTP client (async)."""
95-
await self.client.aclose()
96-
97-
async def get_scopes(self, limit: bool = False) -> Dict[str, Any]:
98-
"""Retrieve available scopes for the current user (async)."""
99-
params = {"limit": int(limit)}
100-
url = f"{self.url}/scopes"
101-
resp = await self.client.get(url=url, headers=self.headers, params=params)
102-
return resp.json()
103-
104-
async def create_token(self, scopes: List[str] = [], ttl: int = 0) -> Dict[str, Any]:
105-
"""Create a new bearer token with specified scopes and TTL (async)."""
106-
payload = {"scopes": scopes, "ttl": ttl}
107-
url = f"{self.url}/token"
108-
resp = await self.client.post(url=url, headers=self.headers, json=payload)
109-
return resp.json()
110-
111-
async def get_token(self, scope: str = None) -> Dict[str, Any]:
112-
"""Retrieve an existing token, optionally filtered by scope (async)."""
113-
params = {"scope": scope or ""}
114-
url = f"{self.url}/token"
115-
resp = await self.client.get(url=url, headers=self.headers, params=params)
116-
return resp.json()
117-
118-
async def delete_token(self, id: str) -> Dict[str, Any]:
119-
"""Revoke/Delete a specific token by ID (async)."""
120-
url = f"{self.url}/token/{id}"
121-
resp = await self.client.delete(url=url, headers=self.headers)
122-
return resp.json()
123-
124-
async def get_counters(self, period: str, date: str) -> Dict[str, Any]:
125-
"""Retrieve usage counters for a specific period and date (async)."""
126-
url = f"{self.url}/counters/{period}/{date}"
127-
resp = await self.client.get(url=url, headers=self.headers)
128-
return resp.json()
6+
# Backward compatibility imports
7+
from .async_client import AsyncClient
8+
from .async_oauth_client import AsyncOauthClient
9+
from .oauth_client import OauthClient
12910

13011

13112
class Client:
@@ -174,64 +55,6 @@ def request(
17455
params=params,
17556
).json()
17657

177-
# Handle cases where the API might return a JSON-encoded string instead of an object
178-
if isinstance(data, str):
179-
try:
180-
data = json.loads(data)
181-
except json.JSONDecodeError:
182-
pass
183-
184-
return data
185-
186-
187-
class AsyncClient:
188-
"""
189-
Asynchronous client for making authenticated requests to Openapi endpoints.
190-
Suitable for use with FastAPI, aiohttp, etc.
191-
"""
192-
193-
def __init__(self, token: str):
194-
self.client = httpx.AsyncClient()
195-
self.auth_header: str = f"Bearer {token}"
196-
self.headers: Dict[str, str] = {
197-
"Authorization": self.auth_header,
198-
"Content-Type": "application/json",
199-
}
200-
201-
async def __aenter__(self):
202-
"""Enable use as an asynchronous context manager."""
203-
return self
204-
205-
async def __aexit__(self, exc_type, exc_val, exc_tb):
206-
"""Ensure the underlying HTTP client is closed on exit (async)."""
207-
await self.client.aclose()
208-
209-
async def aclose(self):
210-
"""Manually close the underlying HTTP client (async)."""
211-
await self.client.aclose()
212-
213-
async def request(
214-
self,
215-
method: str = "GET",
216-
url: str = None,
217-
payload: Dict[str, Any] = None,
218-
params: Dict[str, Any] = None,
219-
) -> Dict[str, Any]:
220-
"""
221-
Make an asynchronous HTTP request to the specified Openapi endpoint.
222-
"""
223-
payload = payload or {}
224-
params = params or {}
225-
url = url or ""
226-
resp = await self.client.request(
227-
method=method,
228-
url=url,
229-
headers=self.headers,
230-
json=payload,
231-
params=params,
232-
)
233-
data = resp.json()
234-
23558
# Handle cases where the API might return a JSON-encoded string instead of an object
23659
if isinstance(data, str):
23760
try:

0 commit comments

Comments
 (0)