jsondb-cloud 1.0.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- jsondb_cloud/__init__.py +65 -0
- jsondb_cloud/_http.py +244 -0
- jsondb_cloud/client.py +177 -0
- jsondb_cloud/collection.py +500 -0
- jsondb_cloud/errors.py +171 -0
- jsondb_cloud/models.py +153 -0
- jsondb_cloud/py.typed +0 -0
- jsondb_cloud/types.py +134 -0
- jsondb_cloud-1.0.4.dist-info/METADATA +152 -0
- jsondb_cloud-1.0.4.dist-info/RECORD +11 -0
- jsondb_cloud-1.0.4.dist-info/WHEEL +4 -0
jsondb_cloud/__init__.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""jsondb-cloud — The official Python SDK for jsondb.cloud.
|
|
2
|
+
|
|
3
|
+
Quick start::
|
|
4
|
+
|
|
5
|
+
from jsondb_cloud import JsonDB
|
|
6
|
+
|
|
7
|
+
db = JsonDB(api_key="jdb_sk_live_xxxx")
|
|
8
|
+
users = db.collection("users")
|
|
9
|
+
|
|
10
|
+
user = users.create({"name": "Alice", "email": "alice@example.com"})
|
|
11
|
+
print(user["_id"])
|
|
12
|
+
|
|
13
|
+
Async usage::
|
|
14
|
+
|
|
15
|
+
from jsondb_cloud import AsyncJsonDB
|
|
16
|
+
|
|
17
|
+
async with AsyncJsonDB(api_key="jdb_sk_live_xxxx") as db:
|
|
18
|
+
users = db.collection("users")
|
|
19
|
+
user = await users.create({"name": "Alice"})
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from .client import AsyncJsonDB, JsonDB
|
|
23
|
+
from .collection import AsyncCollection, Collection
|
|
24
|
+
from .errors import (
|
|
25
|
+
ConflictError,
|
|
26
|
+
DocumentTooLargeError,
|
|
27
|
+
ForbiddenError,
|
|
28
|
+
JsonDBError,
|
|
29
|
+
NotFoundError,
|
|
30
|
+
QuotaExceededError,
|
|
31
|
+
RateLimitError,
|
|
32
|
+
ServerError,
|
|
33
|
+
UnauthorizedError,
|
|
34
|
+
ValidationError,
|
|
35
|
+
create_error,
|
|
36
|
+
)
|
|
37
|
+
from .models import BulkResult, BulkResultSummary, ListResult, Meta
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
# Clients
|
|
41
|
+
"JsonDB",
|
|
42
|
+
"AsyncJsonDB",
|
|
43
|
+
# Collections
|
|
44
|
+
"Collection",
|
|
45
|
+
"AsyncCollection",
|
|
46
|
+
# Response models
|
|
47
|
+
"ListResult",
|
|
48
|
+
"Meta",
|
|
49
|
+
"BulkResult",
|
|
50
|
+
"BulkResultSummary",
|
|
51
|
+
# Errors
|
|
52
|
+
"JsonDBError",
|
|
53
|
+
"NotFoundError",
|
|
54
|
+
"ConflictError",
|
|
55
|
+
"ValidationError",
|
|
56
|
+
"UnauthorizedError",
|
|
57
|
+
"ForbiddenError",
|
|
58
|
+
"QuotaExceededError",
|
|
59
|
+
"RateLimitError",
|
|
60
|
+
"DocumentTooLargeError",
|
|
61
|
+
"ServerError",
|
|
62
|
+
"create_error",
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
__version__ = "1.0.0"
|
jsondb_cloud/_http.py
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"""Low-level HTTP client wrappers with auto-retry for the jsondb.cloud API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import math
|
|
7
|
+
import time
|
|
8
|
+
from typing import Any, Dict, Optional
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
|
|
12
|
+
from .errors import JsonDBError, create_error
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# HTTP status codes that should trigger an automatic retry.
|
|
16
|
+
_RETRYABLE_STATUSES = frozenset({429, 500, 502, 503, 504})
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _backoff_delay(attempt: int, base_delay: float, max_delay: float) -> float:
|
|
20
|
+
"""Calculate exponential backoff delay with jitter cap.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
attempt: Zero-based attempt index (0 = first retry).
|
|
24
|
+
base_delay: Base delay in seconds.
|
|
25
|
+
max_delay: Maximum delay in seconds.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Delay in seconds before the next retry.
|
|
29
|
+
"""
|
|
30
|
+
return min(base_delay * math.pow(2, attempt), max_delay)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class SyncHTTPClient:
|
|
34
|
+
"""Synchronous HTTP client wrapper around ``httpx.Client``.
|
|
35
|
+
|
|
36
|
+
Handles Bearer token auth, automatic retries on 429/5xx with exponential
|
|
37
|
+
backoff, and translation of error responses into ``JsonDBError`` subclasses.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(
|
|
41
|
+
self,
|
|
42
|
+
*,
|
|
43
|
+
api_key: str,
|
|
44
|
+
base_url: str = "https://api.jsondb.cloud",
|
|
45
|
+
max_retries: int = 3,
|
|
46
|
+
retry_base_delay: float = 1.0,
|
|
47
|
+
retry_max_delay: float = 10.0,
|
|
48
|
+
timeout: float = 30.0,
|
|
49
|
+
headers: Optional[Dict[str, str]] = None,
|
|
50
|
+
) -> None:
|
|
51
|
+
self._api_key = api_key
|
|
52
|
+
self._base_url = base_url.rstrip("/")
|
|
53
|
+
self._max_retries = max_retries
|
|
54
|
+
self._retry_base_delay = retry_base_delay
|
|
55
|
+
self._retry_max_delay = retry_max_delay
|
|
56
|
+
|
|
57
|
+
default_headers = {
|
|
58
|
+
"Authorization": f"Bearer {api_key}",
|
|
59
|
+
"Content-Type": "application/json",
|
|
60
|
+
"Accept": "application/json",
|
|
61
|
+
}
|
|
62
|
+
if headers:
|
|
63
|
+
default_headers.update(headers)
|
|
64
|
+
|
|
65
|
+
self._client = httpx.Client(
|
|
66
|
+
base_url=self._base_url,
|
|
67
|
+
headers=default_headers,
|
|
68
|
+
timeout=httpx.Timeout(timeout),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def close(self) -> None:
|
|
72
|
+
"""Close the underlying HTTP client."""
|
|
73
|
+
self._client.close()
|
|
74
|
+
|
|
75
|
+
def request(
|
|
76
|
+
self,
|
|
77
|
+
method: str,
|
|
78
|
+
path: str,
|
|
79
|
+
*,
|
|
80
|
+
json: Any = None,
|
|
81
|
+
headers: Optional[Dict[str, str]] = None,
|
|
82
|
+
) -> Any:
|
|
83
|
+
"""Execute an HTTP request with automatic retry.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
method: HTTP method (``GET``, ``POST``, ``PUT``, ``PATCH``, ``DELETE``).
|
|
87
|
+
path: URL path relative to ``base_url`` (should start with ``/``).
|
|
88
|
+
json: JSON-serializable request body (for POST/PUT/PATCH).
|
|
89
|
+
headers: Extra headers to merge for this request only.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Parsed JSON response body, or ``None`` for 204 responses.
|
|
93
|
+
|
|
94
|
+
Raises:
|
|
95
|
+
JsonDBError: On non-retryable API errors.
|
|
96
|
+
"""
|
|
97
|
+
max_attempts = self._max_retries + 1
|
|
98
|
+
last_error: Optional[Exception] = None
|
|
99
|
+
|
|
100
|
+
for attempt in range(max_attempts):
|
|
101
|
+
try:
|
|
102
|
+
response = self._client.request(
|
|
103
|
+
method,
|
|
104
|
+
path,
|
|
105
|
+
json=json,
|
|
106
|
+
headers=headers,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Retry on retryable status codes
|
|
110
|
+
if response.status_code in _RETRYABLE_STATUSES and attempt < max_attempts - 1:
|
|
111
|
+
delay = _backoff_delay(attempt, self._retry_base_delay, self._retry_max_delay)
|
|
112
|
+
time.sleep(delay)
|
|
113
|
+
continue
|
|
114
|
+
|
|
115
|
+
# No content
|
|
116
|
+
if response.status_code == 204:
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
data = response.json()
|
|
120
|
+
|
|
121
|
+
if not response.is_success:
|
|
122
|
+
raise create_error(response.status_code, data)
|
|
123
|
+
|
|
124
|
+
return data
|
|
125
|
+
|
|
126
|
+
except JsonDBError:
|
|
127
|
+
raise
|
|
128
|
+
except Exception as exc:
|
|
129
|
+
last_error = exc
|
|
130
|
+
if attempt < max_attempts - 1:
|
|
131
|
+
delay = _backoff_delay(attempt, self._retry_base_delay, self._retry_max_delay)
|
|
132
|
+
time.sleep(delay)
|
|
133
|
+
continue
|
|
134
|
+
|
|
135
|
+
if last_error is not None:
|
|
136
|
+
raise last_error
|
|
137
|
+
raise JsonDBError("Request failed after retries") # pragma: no cover
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class AsyncHTTPClient:
|
|
141
|
+
"""Asynchronous HTTP client wrapper around ``httpx.AsyncClient``.
|
|
142
|
+
|
|
143
|
+
Handles Bearer token auth, automatic retries on 429/5xx with exponential
|
|
144
|
+
backoff, and translation of error responses into ``JsonDBError`` subclasses.
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
def __init__(
|
|
148
|
+
self,
|
|
149
|
+
*,
|
|
150
|
+
api_key: str,
|
|
151
|
+
base_url: str = "https://api.jsondb.cloud",
|
|
152
|
+
max_retries: int = 3,
|
|
153
|
+
retry_base_delay: float = 1.0,
|
|
154
|
+
retry_max_delay: float = 10.0,
|
|
155
|
+
timeout: float = 30.0,
|
|
156
|
+
headers: Optional[Dict[str, str]] = None,
|
|
157
|
+
) -> None:
|
|
158
|
+
self._api_key = api_key
|
|
159
|
+
self._base_url = base_url.rstrip("/")
|
|
160
|
+
self._max_retries = max_retries
|
|
161
|
+
self._retry_base_delay = retry_base_delay
|
|
162
|
+
self._retry_max_delay = retry_max_delay
|
|
163
|
+
|
|
164
|
+
default_headers = {
|
|
165
|
+
"Authorization": f"Bearer {api_key}",
|
|
166
|
+
"Content-Type": "application/json",
|
|
167
|
+
"Accept": "application/json",
|
|
168
|
+
}
|
|
169
|
+
if headers:
|
|
170
|
+
default_headers.update(headers)
|
|
171
|
+
|
|
172
|
+
self._client = httpx.AsyncClient(
|
|
173
|
+
base_url=self._base_url,
|
|
174
|
+
headers=default_headers,
|
|
175
|
+
timeout=httpx.Timeout(timeout),
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
async def close(self) -> None:
|
|
179
|
+
"""Close the underlying async HTTP client."""
|
|
180
|
+
await self._client.aclose()
|
|
181
|
+
|
|
182
|
+
async def request(
|
|
183
|
+
self,
|
|
184
|
+
method: str,
|
|
185
|
+
path: str,
|
|
186
|
+
*,
|
|
187
|
+
json: Any = None,
|
|
188
|
+
headers: Optional[Dict[str, str]] = None,
|
|
189
|
+
) -> Any:
|
|
190
|
+
"""Execute an async HTTP request with automatic retry.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
method: HTTP method (``GET``, ``POST``, ``PUT``, ``PATCH``, ``DELETE``).
|
|
194
|
+
path: URL path relative to ``base_url`` (should start with ``/``).
|
|
195
|
+
json: JSON-serializable request body (for POST/PUT/PATCH).
|
|
196
|
+
headers: Extra headers to merge for this request only.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
Parsed JSON response body, or ``None`` for 204 responses.
|
|
200
|
+
|
|
201
|
+
Raises:
|
|
202
|
+
JsonDBError: On non-retryable API errors.
|
|
203
|
+
"""
|
|
204
|
+
max_attempts = self._max_retries + 1
|
|
205
|
+
last_error: Optional[Exception] = None
|
|
206
|
+
|
|
207
|
+
for attempt in range(max_attempts):
|
|
208
|
+
try:
|
|
209
|
+
response = await self._client.request(
|
|
210
|
+
method,
|
|
211
|
+
path,
|
|
212
|
+
json=json,
|
|
213
|
+
headers=headers,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
# Retry on retryable status codes
|
|
217
|
+
if response.status_code in _RETRYABLE_STATUSES and attempt < max_attempts - 1:
|
|
218
|
+
delay = _backoff_delay(attempt, self._retry_base_delay, self._retry_max_delay)
|
|
219
|
+
await asyncio.sleep(delay)
|
|
220
|
+
continue
|
|
221
|
+
|
|
222
|
+
# No content
|
|
223
|
+
if response.status_code == 204:
|
|
224
|
+
return None
|
|
225
|
+
|
|
226
|
+
data = response.json()
|
|
227
|
+
|
|
228
|
+
if not response.is_success:
|
|
229
|
+
raise create_error(response.status_code, data)
|
|
230
|
+
|
|
231
|
+
return data
|
|
232
|
+
|
|
233
|
+
except JsonDBError:
|
|
234
|
+
raise
|
|
235
|
+
except Exception as exc:
|
|
236
|
+
last_error = exc
|
|
237
|
+
if attempt < max_attempts - 1:
|
|
238
|
+
delay = _backoff_delay(attempt, self._retry_base_delay, self._retry_max_delay)
|
|
239
|
+
await asyncio.sleep(delay)
|
|
240
|
+
continue
|
|
241
|
+
|
|
242
|
+
if last_error is not None:
|
|
243
|
+
raise last_error
|
|
244
|
+
raise JsonDBError("Request failed after retries") # pragma: no cover
|
jsondb_cloud/client.py
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""Client classes for the jsondb.cloud Python SDK."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict, List, Optional, cast
|
|
6
|
+
|
|
7
|
+
from ._http import AsyncHTTPClient, SyncHTTPClient
|
|
8
|
+
from .collection import AsyncCollection, Collection
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class JsonDB:
|
|
12
|
+
"""Synchronous client for the jsondb.cloud API.
|
|
13
|
+
|
|
14
|
+
Usage::
|
|
15
|
+
|
|
16
|
+
from jsondb_cloud import JsonDB
|
|
17
|
+
|
|
18
|
+
db = JsonDB(api_key="jdb_sk_live_xxxx")
|
|
19
|
+
users = db.collection("users")
|
|
20
|
+
|
|
21
|
+
# Create a document
|
|
22
|
+
alice = users.create({"name": "Alice", "email": "alice@example.com"})
|
|
23
|
+
|
|
24
|
+
# Read it back
|
|
25
|
+
user = users.get(alice["_id"])
|
|
26
|
+
|
|
27
|
+
Supports use as a context manager::
|
|
28
|
+
|
|
29
|
+
with JsonDB(api_key="jdb_sk_live_xxxx") as db:
|
|
30
|
+
users = db.collection("users")
|
|
31
|
+
users.create({"name": "Alice"})
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
api_key: API key (``jdb_sk_live_*`` or ``jdb_sk_test_*``).
|
|
35
|
+
project: Project name. Defaults to ``"v1"``.
|
|
36
|
+
base_url: API base URL. Defaults to ``"https://api.jsondb.cloud"``.
|
|
37
|
+
max_retries: Maximum number of retries on 429/5xx. Defaults to ``3``.
|
|
38
|
+
retry_base_delay: Base delay in seconds for exponential backoff. Defaults to ``1.0``.
|
|
39
|
+
retry_max_delay: Maximum delay in seconds for exponential backoff. Defaults to ``10.0``.
|
|
40
|
+
timeout: Request timeout in seconds. Defaults to ``30.0``.
|
|
41
|
+
headers: Optional extra headers to include with every request.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
api_key: str,
|
|
47
|
+
*,
|
|
48
|
+
project: str = "v1",
|
|
49
|
+
base_url: str = "https://api.jsondb.cloud",
|
|
50
|
+
max_retries: int = 3,
|
|
51
|
+
retry_base_delay: float = 1.0,
|
|
52
|
+
retry_max_delay: float = 10.0,
|
|
53
|
+
timeout: float = 30.0,
|
|
54
|
+
headers: Optional[Dict[str, str]] = None,
|
|
55
|
+
) -> None:
|
|
56
|
+
if not api_key:
|
|
57
|
+
raise ValueError("api_key is required")
|
|
58
|
+
|
|
59
|
+
self._project = project
|
|
60
|
+
self._http = SyncHTTPClient(
|
|
61
|
+
api_key=api_key,
|
|
62
|
+
base_url=base_url,
|
|
63
|
+
max_retries=max_retries,
|
|
64
|
+
retry_base_delay=retry_base_delay,
|
|
65
|
+
retry_max_delay=retry_max_delay,
|
|
66
|
+
timeout=timeout,
|
|
67
|
+
headers=headers,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
def collection(self, name: str) -> Collection:
|
|
71
|
+
"""Get a reference to a collection.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
name: Collection name (e.g. ``"users"``, ``"posts"``).
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
A :class:`Collection` instance bound to this client and project.
|
|
78
|
+
"""
|
|
79
|
+
return Collection(name=name, project=self._project, http=self._http)
|
|
80
|
+
|
|
81
|
+
def list_collections(self) -> List[str]:
|
|
82
|
+
"""List all collections in the project."""
|
|
83
|
+
data = self._http.request("GET", f"/{self._project}")
|
|
84
|
+
return cast(List[str], data.get("data", data) if isinstance(data, dict) else data)
|
|
85
|
+
|
|
86
|
+
def close(self) -> None:
|
|
87
|
+
"""Close the underlying HTTP client and release resources."""
|
|
88
|
+
self._http.close()
|
|
89
|
+
|
|
90
|
+
def __enter__(self) -> "JsonDB":
|
|
91
|
+
return self
|
|
92
|
+
|
|
93
|
+
def __exit__(self, *args: Any) -> None:
|
|
94
|
+
self.close()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class AsyncJsonDB:
|
|
98
|
+
"""Asynchronous client for the jsondb.cloud API.
|
|
99
|
+
|
|
100
|
+
Usage::
|
|
101
|
+
|
|
102
|
+
from jsondb_cloud import AsyncJsonDB
|
|
103
|
+
|
|
104
|
+
db = AsyncJsonDB(api_key="jdb_sk_live_xxxx")
|
|
105
|
+
users = db.collection("users")
|
|
106
|
+
|
|
107
|
+
alice = await users.create({"name": "Alice", "email": "alice@example.com"})
|
|
108
|
+
user = await users.get(alice["_id"])
|
|
109
|
+
|
|
110
|
+
Supports use as an async context manager::
|
|
111
|
+
|
|
112
|
+
async with AsyncJsonDB(api_key="jdb_sk_live_xxxx") as db:
|
|
113
|
+
users = db.collection("users")
|
|
114
|
+
await users.create({"name": "Alice"})
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
api_key: API key (``jdb_sk_live_*`` or ``jdb_sk_test_*``).
|
|
118
|
+
project: Project name. Defaults to ``"v1"``.
|
|
119
|
+
base_url: API base URL. Defaults to ``"https://api.jsondb.cloud"``.
|
|
120
|
+
max_retries: Maximum number of retries on 429/5xx. Defaults to ``3``.
|
|
121
|
+
retry_base_delay: Base delay in seconds for exponential backoff. Defaults to ``1.0``.
|
|
122
|
+
retry_max_delay: Maximum delay in seconds for exponential backoff. Defaults to ``10.0``.
|
|
123
|
+
timeout: Request timeout in seconds. Defaults to ``30.0``.
|
|
124
|
+
headers: Optional extra headers to include with every request.
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
def __init__(
|
|
128
|
+
self,
|
|
129
|
+
api_key: str,
|
|
130
|
+
*,
|
|
131
|
+
project: str = "v1",
|
|
132
|
+
base_url: str = "https://api.jsondb.cloud",
|
|
133
|
+
max_retries: int = 3,
|
|
134
|
+
retry_base_delay: float = 1.0,
|
|
135
|
+
retry_max_delay: float = 10.0,
|
|
136
|
+
timeout: float = 30.0,
|
|
137
|
+
headers: Optional[Dict[str, str]] = None,
|
|
138
|
+
) -> None:
|
|
139
|
+
if not api_key:
|
|
140
|
+
raise ValueError("api_key is required")
|
|
141
|
+
|
|
142
|
+
self._project = project
|
|
143
|
+
self._http = AsyncHTTPClient(
|
|
144
|
+
api_key=api_key,
|
|
145
|
+
base_url=base_url,
|
|
146
|
+
max_retries=max_retries,
|
|
147
|
+
retry_base_delay=retry_base_delay,
|
|
148
|
+
retry_max_delay=retry_max_delay,
|
|
149
|
+
timeout=timeout,
|
|
150
|
+
headers=headers,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
def collection(self, name: str) -> AsyncCollection:
|
|
154
|
+
"""Get a reference to a collection.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
name: Collection name (e.g. ``"users"``, ``"posts"``).
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
An :class:`AsyncCollection` instance bound to this client and project.
|
|
161
|
+
"""
|
|
162
|
+
return AsyncCollection(name=name, project=self._project, http=self._http)
|
|
163
|
+
|
|
164
|
+
async def list_collections(self) -> List[str]:
|
|
165
|
+
"""List all collections in the project."""
|
|
166
|
+
data = await self._http.request("GET", f"/{self._project}")
|
|
167
|
+
return cast(List[str], data.get("data", data) if isinstance(data, dict) else data)
|
|
168
|
+
|
|
169
|
+
async def close(self) -> None:
|
|
170
|
+
"""Close the underlying async HTTP client and release resources."""
|
|
171
|
+
await self._http.close()
|
|
172
|
+
|
|
173
|
+
async def __aenter__(self) -> "AsyncJsonDB":
|
|
174
|
+
return self
|
|
175
|
+
|
|
176
|
+
async def __aexit__(self, *args: Any) -> None:
|
|
177
|
+
await self.close()
|