defistream 1.0.6__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.
- defistream/__init__.py +51 -0
- defistream/client.py +380 -0
- defistream/exceptions.py +65 -0
- defistream/models.py +27 -0
- defistream/protocols.py +449 -0
- defistream/py.typed +0 -0
- defistream/query.py +441 -0
- defistream-1.0.6.dist-info/METADATA +438 -0
- defistream-1.0.6.dist-info/RECORD +11 -0
- defistream-1.0.6.dist-info/WHEEL +4 -0
- defistream-1.0.6.dist-info/licenses/LICENSE +21 -0
defistream/query.py
ADDED
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
"""Query builder for DeFiStream API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from .client import BaseClient
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class QueryBuilder:
|
|
12
|
+
"""
|
|
13
|
+
Builder for constructing and executing DeFiStream API queries.
|
|
14
|
+
|
|
15
|
+
Query is only executed when a terminal method is called:
|
|
16
|
+
- as_dict() - returns list of dictionaries
|
|
17
|
+
- as_df() - returns pandas DataFrame (default) or polars DataFrame
|
|
18
|
+
- as_file() - saves to CSV, Parquet, or JSON file
|
|
19
|
+
|
|
20
|
+
Example:
|
|
21
|
+
query = client.erc20.transfers("USDT").network("ETH").block_range(21000000, 21010000)
|
|
22
|
+
query = query.min_amount(1000).sender("0x...")
|
|
23
|
+
df = query.as_df() # pandas DataFrame
|
|
24
|
+
df = query.as_df("polars") # polars DataFrame
|
|
25
|
+
query.as_file("transfers.csv") # save to CSV
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
client: "BaseClient",
|
|
31
|
+
endpoint: str,
|
|
32
|
+
initial_params: dict[str, Any] | None = None,
|
|
33
|
+
):
|
|
34
|
+
self._client = client
|
|
35
|
+
self._endpoint = endpoint
|
|
36
|
+
self._params: dict[str, Any] = initial_params or {}
|
|
37
|
+
self._verbose = False
|
|
38
|
+
|
|
39
|
+
def _copy_with(self, **updates: Any) -> "QueryBuilder":
|
|
40
|
+
"""Create a copy with updated parameters."""
|
|
41
|
+
new_builder = QueryBuilder(self._client, self._endpoint, self._params.copy())
|
|
42
|
+
new_builder._verbose = self._verbose
|
|
43
|
+
for key, value in updates.items():
|
|
44
|
+
if key == "verbose":
|
|
45
|
+
new_builder._verbose = value
|
|
46
|
+
elif value is not None:
|
|
47
|
+
new_builder._params[key] = value
|
|
48
|
+
return new_builder
|
|
49
|
+
|
|
50
|
+
# Network and block range
|
|
51
|
+
def network(self, network: str) -> "QueryBuilder":
|
|
52
|
+
"""Set the network (ETH, ARB, BASE, OP, POLYGON, etc.)."""
|
|
53
|
+
return self._copy_with(network=network)
|
|
54
|
+
|
|
55
|
+
def start_block(self, block: int) -> "QueryBuilder":
|
|
56
|
+
"""Set the starting block number."""
|
|
57
|
+
return self._copy_with(block_start=block)
|
|
58
|
+
|
|
59
|
+
def end_block(self, block: int) -> "QueryBuilder":
|
|
60
|
+
"""Set the ending block number."""
|
|
61
|
+
return self._copy_with(block_end=block)
|
|
62
|
+
|
|
63
|
+
def block_range(self, start: int, end: int) -> "QueryBuilder":
|
|
64
|
+
"""Set both start and end block numbers."""
|
|
65
|
+
return self._copy_with(block_start=start, block_end=end)
|
|
66
|
+
|
|
67
|
+
# Time range
|
|
68
|
+
def start_time(self, timestamp: str) -> "QueryBuilder":
|
|
69
|
+
"""Set the starting time (ISO format or Unix timestamp)."""
|
|
70
|
+
return self._copy_with(since=timestamp)
|
|
71
|
+
|
|
72
|
+
def end_time(self, timestamp: str) -> "QueryBuilder":
|
|
73
|
+
"""Set the ending time (ISO format or Unix timestamp)."""
|
|
74
|
+
return self._copy_with(until=timestamp)
|
|
75
|
+
|
|
76
|
+
def time_range(self, start: str, end: str) -> "QueryBuilder":
|
|
77
|
+
"""Set both start and end times."""
|
|
78
|
+
return self._copy_with(since=start, until=end)
|
|
79
|
+
|
|
80
|
+
# ERC20 and Native Token filters
|
|
81
|
+
def sender(self, address: str) -> "QueryBuilder":
|
|
82
|
+
"""Filter by sender address (ERC20, Native Token)."""
|
|
83
|
+
return self._copy_with(sender=address)
|
|
84
|
+
|
|
85
|
+
def receiver(self, address: str) -> "QueryBuilder":
|
|
86
|
+
"""Filter by receiver address (ERC20, Native Token)."""
|
|
87
|
+
return self._copy_with(receiver=address)
|
|
88
|
+
|
|
89
|
+
def from_address(self, address: str) -> "QueryBuilder":
|
|
90
|
+
"""Filter by sender address (alias for sender)."""
|
|
91
|
+
return self.sender(address)
|
|
92
|
+
|
|
93
|
+
def to_address(self, address: str) -> "QueryBuilder":
|
|
94
|
+
"""Filter by receiver address (alias for receiver)."""
|
|
95
|
+
return self.receiver(address)
|
|
96
|
+
|
|
97
|
+
def min_amount(self, amount: float) -> "QueryBuilder":
|
|
98
|
+
"""Filter by minimum amount (ERC20, Native Token)."""
|
|
99
|
+
return self._copy_with(min_amount=amount)
|
|
100
|
+
|
|
101
|
+
def max_amount(self, amount: float) -> "QueryBuilder":
|
|
102
|
+
"""Filter by maximum amount (ERC20, Native Token)."""
|
|
103
|
+
return self._copy_with(max_amount=amount)
|
|
104
|
+
|
|
105
|
+
# ERC20 specific
|
|
106
|
+
def token(self, symbol: str) -> "QueryBuilder":
|
|
107
|
+
"""Set token symbol or address (ERC20)."""
|
|
108
|
+
return self._copy_with(token=symbol)
|
|
109
|
+
|
|
110
|
+
# AAVE specific
|
|
111
|
+
def eth_market_type(self, market_type: str) -> "QueryBuilder":
|
|
112
|
+
"""Set AAVE market type for ETH network: 'Core', 'Prime', or 'EtherFi'. Default: 'Core'."""
|
|
113
|
+
return self._copy_with(eth_market_type=market_type)
|
|
114
|
+
|
|
115
|
+
# Uniswap specific
|
|
116
|
+
def symbol0(self, symbol: str) -> "QueryBuilder":
|
|
117
|
+
"""Set first token symbol (Uniswap)."""
|
|
118
|
+
return self._copy_with(symbol0=symbol)
|
|
119
|
+
|
|
120
|
+
def symbol1(self, symbol: str) -> "QueryBuilder":
|
|
121
|
+
"""Set second token symbol (Uniswap)."""
|
|
122
|
+
return self._copy_with(symbol1=symbol)
|
|
123
|
+
|
|
124
|
+
def fee(self, fee_tier: int) -> "QueryBuilder":
|
|
125
|
+
"""Set fee tier (Uniswap): 100, 500, 3000, 10000."""
|
|
126
|
+
return self._copy_with(fee=fee_tier)
|
|
127
|
+
|
|
128
|
+
# Verbose mode
|
|
129
|
+
def verbose(self, enabled: bool = True) -> "QueryBuilder":
|
|
130
|
+
"""Include all metadata fields (tx_hash, tx_id, log_index, network, name)."""
|
|
131
|
+
return self._copy_with(verbose=enabled)
|
|
132
|
+
|
|
133
|
+
# Build final params
|
|
134
|
+
def _build_params(self) -> dict[str, Any]:
|
|
135
|
+
"""Build the final query parameters."""
|
|
136
|
+
params = self._params.copy()
|
|
137
|
+
if self._verbose:
|
|
138
|
+
params["verbose"] = "true"
|
|
139
|
+
return params
|
|
140
|
+
|
|
141
|
+
# Terminal methods - execute the query
|
|
142
|
+
def as_dict(self) -> list[dict[str, Any]]:
|
|
143
|
+
"""
|
|
144
|
+
Execute query and return results as list of dictionaries.
|
|
145
|
+
|
|
146
|
+
Uses JSON format from API. Limited to 10,000 blocks.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
List of event dictionaries
|
|
150
|
+
"""
|
|
151
|
+
params = self._build_params()
|
|
152
|
+
return self._client._request("GET", self._endpoint, params=params)
|
|
153
|
+
|
|
154
|
+
def as_df(self, library: str = "pandas") -> Any:
|
|
155
|
+
"""
|
|
156
|
+
Execute query and return results as DataFrame.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
library: DataFrame library to use - "pandas" (default) or "polars"
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
pandas.DataFrame or polars.DataFrame
|
|
163
|
+
|
|
164
|
+
Example:
|
|
165
|
+
df = query.as_df() # pandas DataFrame
|
|
166
|
+
df = query.as_df("polars") # polars DataFrame
|
|
167
|
+
"""
|
|
168
|
+
if library not in ("pandas", "polars"):
|
|
169
|
+
raise ValueError(f"library must be 'pandas' or 'polars', got '{library}'")
|
|
170
|
+
params = self._build_params()
|
|
171
|
+
params["format"] = "parquet"
|
|
172
|
+
return self._client._request(
|
|
173
|
+
"GET", self._endpoint, params=params, as_dataframe=library
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
def as_file(self, path: str, format: str | None = None) -> None:
|
|
177
|
+
"""
|
|
178
|
+
Execute query and save results to file.
|
|
179
|
+
|
|
180
|
+
Format is automatically determined by file extension, or can be
|
|
181
|
+
explicitly specified.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
path: File path to save to
|
|
185
|
+
format: File format - "csv", "parquet", or "json".
|
|
186
|
+
If None, determined from file extension.
|
|
187
|
+
|
|
188
|
+
Example:
|
|
189
|
+
query.as_file("transfers.csv") # CSV format
|
|
190
|
+
query.as_file("transfers.parquet") # Parquet format
|
|
191
|
+
query.as_file("transfers.json") # JSON format
|
|
192
|
+
query.as_file("transfers", format="csv") # Explicit format
|
|
193
|
+
"""
|
|
194
|
+
# Determine format from extension or explicit parameter
|
|
195
|
+
if format is None:
|
|
196
|
+
if path.endswith(".csv"):
|
|
197
|
+
format = "csv"
|
|
198
|
+
elif path.endswith(".parquet"):
|
|
199
|
+
format = "parquet"
|
|
200
|
+
elif path.endswith(".json"):
|
|
201
|
+
format = "json"
|
|
202
|
+
else:
|
|
203
|
+
raise ValueError(
|
|
204
|
+
f"Cannot determine format from path '{path}'. "
|
|
205
|
+
"Use a file extension (.csv, .parquet, .json) or specify format explicitly."
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
if format not in ("csv", "parquet", "json"):
|
|
209
|
+
raise ValueError(f"format must be 'csv', 'parquet', or 'json', got '{format}'")
|
|
210
|
+
|
|
211
|
+
params = self._build_params()
|
|
212
|
+
|
|
213
|
+
if format == "json":
|
|
214
|
+
# For JSON, fetch as dict and write manually
|
|
215
|
+
import json
|
|
216
|
+
results = self.as_dict()
|
|
217
|
+
with open(path, "w") as f:
|
|
218
|
+
json.dump(results, f, indent=2)
|
|
219
|
+
else:
|
|
220
|
+
# For CSV and Parquet, use API format parameter
|
|
221
|
+
params["format"] = format
|
|
222
|
+
self._client._request("GET", self._endpoint, params=params, output_file=path)
|
|
223
|
+
|
|
224
|
+
def __repr__(self) -> str:
|
|
225
|
+
return f"QueryBuilder(endpoint={self._endpoint!r}, params={self._params!r}, verbose={self._verbose})"
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class AsyncQueryBuilder:
|
|
229
|
+
"""
|
|
230
|
+
Async builder for constructing and executing DeFiStream API queries.
|
|
231
|
+
|
|
232
|
+
Query is only executed when a terminal method is called:
|
|
233
|
+
- as_dict() - returns list of dictionaries
|
|
234
|
+
- as_df() - returns pandas DataFrame (default) or polars DataFrame
|
|
235
|
+
- as_file() - saves to CSV, Parquet, or JSON file
|
|
236
|
+
|
|
237
|
+
Example:
|
|
238
|
+
query = client.erc20.transfers("USDT").network("ETH").block_range(21000000, 21010000)
|
|
239
|
+
df = await query.as_df() # pandas DataFrame
|
|
240
|
+
df = await query.as_df("polars") # polars DataFrame
|
|
241
|
+
await query.as_file("transfers.csv") # save to CSV
|
|
242
|
+
"""
|
|
243
|
+
|
|
244
|
+
def __init__(
|
|
245
|
+
self,
|
|
246
|
+
client: "BaseClient",
|
|
247
|
+
endpoint: str,
|
|
248
|
+
initial_params: dict[str, Any] | None = None,
|
|
249
|
+
):
|
|
250
|
+
self._client = client
|
|
251
|
+
self._endpoint = endpoint
|
|
252
|
+
self._params: dict[str, Any] = initial_params or {}
|
|
253
|
+
self._verbose = False
|
|
254
|
+
|
|
255
|
+
def _copy_with(self, **updates: Any) -> "AsyncQueryBuilder":
|
|
256
|
+
"""Create a copy with updated parameters."""
|
|
257
|
+
new_builder = AsyncQueryBuilder(self._client, self._endpoint, self._params.copy())
|
|
258
|
+
new_builder._verbose = self._verbose
|
|
259
|
+
for key, value in updates.items():
|
|
260
|
+
if key == "verbose":
|
|
261
|
+
new_builder._verbose = value
|
|
262
|
+
elif value is not None:
|
|
263
|
+
new_builder._params[key] = value
|
|
264
|
+
return new_builder
|
|
265
|
+
|
|
266
|
+
# Network and block range
|
|
267
|
+
def network(self, network: str) -> "AsyncQueryBuilder":
|
|
268
|
+
"""Set the network (ETH, ARB, BASE, OP, POLYGON, etc.)."""
|
|
269
|
+
return self._copy_with(network=network)
|
|
270
|
+
|
|
271
|
+
def start_block(self, block: int) -> "AsyncQueryBuilder":
|
|
272
|
+
"""Set the starting block number."""
|
|
273
|
+
return self._copy_with(block_start=block)
|
|
274
|
+
|
|
275
|
+
def end_block(self, block: int) -> "AsyncQueryBuilder":
|
|
276
|
+
"""Set the ending block number."""
|
|
277
|
+
return self._copy_with(block_end=block)
|
|
278
|
+
|
|
279
|
+
def block_range(self, start: int, end: int) -> "AsyncQueryBuilder":
|
|
280
|
+
"""Set both start and end block numbers."""
|
|
281
|
+
return self._copy_with(block_start=start, block_end=end)
|
|
282
|
+
|
|
283
|
+
# Time range
|
|
284
|
+
def start_time(self, timestamp: str) -> "AsyncQueryBuilder":
|
|
285
|
+
"""Set the starting time (ISO format or Unix timestamp)."""
|
|
286
|
+
return self._copy_with(since=timestamp)
|
|
287
|
+
|
|
288
|
+
def end_time(self, timestamp: str) -> "AsyncQueryBuilder":
|
|
289
|
+
"""Set the ending time (ISO format or Unix timestamp)."""
|
|
290
|
+
return self._copy_with(until=timestamp)
|
|
291
|
+
|
|
292
|
+
def time_range(self, start: str, end: str) -> "AsyncQueryBuilder":
|
|
293
|
+
"""Set both start and end times."""
|
|
294
|
+
return self._copy_with(since=start, until=end)
|
|
295
|
+
|
|
296
|
+
# ERC20 and Native Token filters
|
|
297
|
+
def sender(self, address: str) -> "AsyncQueryBuilder":
|
|
298
|
+
"""Filter by sender address (ERC20, Native Token)."""
|
|
299
|
+
return self._copy_with(sender=address)
|
|
300
|
+
|
|
301
|
+
def receiver(self, address: str) -> "AsyncQueryBuilder":
|
|
302
|
+
"""Filter by receiver address (ERC20, Native Token)."""
|
|
303
|
+
return self._copy_with(receiver=address)
|
|
304
|
+
|
|
305
|
+
def from_address(self, address: str) -> "AsyncQueryBuilder":
|
|
306
|
+
"""Filter by sender address (alias for sender)."""
|
|
307
|
+
return self.sender(address)
|
|
308
|
+
|
|
309
|
+
def to_address(self, address: str) -> "AsyncQueryBuilder":
|
|
310
|
+
"""Filter by receiver address (alias for receiver)."""
|
|
311
|
+
return self.receiver(address)
|
|
312
|
+
|
|
313
|
+
def min_amount(self, amount: float) -> "AsyncQueryBuilder":
|
|
314
|
+
"""Filter by minimum amount (ERC20, Native Token)."""
|
|
315
|
+
return self._copy_with(min_amount=amount)
|
|
316
|
+
|
|
317
|
+
def max_amount(self, amount: float) -> "AsyncQueryBuilder":
|
|
318
|
+
"""Filter by maximum amount (ERC20, Native Token)."""
|
|
319
|
+
return self._copy_with(max_amount=amount)
|
|
320
|
+
|
|
321
|
+
# ERC20 specific
|
|
322
|
+
def token(self, symbol: str) -> "AsyncQueryBuilder":
|
|
323
|
+
"""Set token symbol or address (ERC20)."""
|
|
324
|
+
return self._copy_with(token=symbol)
|
|
325
|
+
|
|
326
|
+
# AAVE specific
|
|
327
|
+
def eth_market_type(self, market_type: str) -> "AsyncQueryBuilder":
|
|
328
|
+
"""Set AAVE market type for ETH network: 'Core', 'Prime', or 'EtherFi'. Default: 'Core'."""
|
|
329
|
+
return self._copy_with(eth_market_type=market_type)
|
|
330
|
+
|
|
331
|
+
# Uniswap specific
|
|
332
|
+
def symbol0(self, symbol: str) -> "AsyncQueryBuilder":
|
|
333
|
+
"""Set first token symbol (Uniswap)."""
|
|
334
|
+
return self._copy_with(symbol0=symbol)
|
|
335
|
+
|
|
336
|
+
def symbol1(self, symbol: str) -> "AsyncQueryBuilder":
|
|
337
|
+
"""Set second token symbol (Uniswap)."""
|
|
338
|
+
return self._copy_with(symbol1=symbol)
|
|
339
|
+
|
|
340
|
+
def fee(self, fee_tier: int) -> "AsyncQueryBuilder":
|
|
341
|
+
"""Set fee tier (Uniswap): 100, 500, 3000, 10000."""
|
|
342
|
+
return self._copy_with(fee=fee_tier)
|
|
343
|
+
|
|
344
|
+
# Verbose mode
|
|
345
|
+
def verbose(self, enabled: bool = True) -> "AsyncQueryBuilder":
|
|
346
|
+
"""Include all metadata fields (tx_hash, tx_id, log_index, network, name)."""
|
|
347
|
+
return self._copy_with(verbose=enabled)
|
|
348
|
+
|
|
349
|
+
# Build final params
|
|
350
|
+
def _build_params(self) -> dict[str, Any]:
|
|
351
|
+
"""Build the final query parameters."""
|
|
352
|
+
params = self._params.copy()
|
|
353
|
+
if self._verbose:
|
|
354
|
+
params["verbose"] = "true"
|
|
355
|
+
return params
|
|
356
|
+
|
|
357
|
+
# Terminal methods - execute the query (async)
|
|
358
|
+
async def as_dict(self) -> list[dict[str, Any]]:
|
|
359
|
+
"""
|
|
360
|
+
Execute query and return results as list of dictionaries.
|
|
361
|
+
|
|
362
|
+
Uses JSON format from API. Limited to 10,000 blocks.
|
|
363
|
+
|
|
364
|
+
Returns:
|
|
365
|
+
List of event dictionaries
|
|
366
|
+
"""
|
|
367
|
+
params = self._build_params()
|
|
368
|
+
return await self._client._request("GET", self._endpoint, params=params)
|
|
369
|
+
|
|
370
|
+
async def as_df(self, library: str = "pandas") -> Any:
|
|
371
|
+
"""
|
|
372
|
+
Execute query and return results as DataFrame.
|
|
373
|
+
|
|
374
|
+
Args:
|
|
375
|
+
library: DataFrame library to use - "pandas" (default) or "polars"
|
|
376
|
+
|
|
377
|
+
Returns:
|
|
378
|
+
pandas.DataFrame or polars.DataFrame
|
|
379
|
+
|
|
380
|
+
Example:
|
|
381
|
+
df = await query.as_df() # pandas DataFrame
|
|
382
|
+
df = await query.as_df("polars") # polars DataFrame
|
|
383
|
+
"""
|
|
384
|
+
if library not in ("pandas", "polars"):
|
|
385
|
+
raise ValueError(f"library must be 'pandas' or 'polars', got '{library}'")
|
|
386
|
+
params = self._build_params()
|
|
387
|
+
params["format"] = "parquet"
|
|
388
|
+
return await self._client._request(
|
|
389
|
+
"GET", self._endpoint, params=params, as_dataframe=library
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
async def as_file(self, path: str, format: str | None = None) -> None:
|
|
393
|
+
"""
|
|
394
|
+
Execute query and save results to file.
|
|
395
|
+
|
|
396
|
+
Format is automatically determined by file extension, or can be
|
|
397
|
+
explicitly specified.
|
|
398
|
+
|
|
399
|
+
Args:
|
|
400
|
+
path: File path to save to
|
|
401
|
+
format: File format - "csv", "parquet", or "json".
|
|
402
|
+
If None, determined from file extension.
|
|
403
|
+
|
|
404
|
+
Example:
|
|
405
|
+
await query.as_file("transfers.csv") # CSV format
|
|
406
|
+
await query.as_file("transfers.parquet") # Parquet format
|
|
407
|
+
await query.as_file("transfers.json") # JSON format
|
|
408
|
+
await query.as_file("transfers", format="csv") # Explicit format
|
|
409
|
+
"""
|
|
410
|
+
# Determine format from extension or explicit parameter
|
|
411
|
+
if format is None:
|
|
412
|
+
if path.endswith(".csv"):
|
|
413
|
+
format = "csv"
|
|
414
|
+
elif path.endswith(".parquet"):
|
|
415
|
+
format = "parquet"
|
|
416
|
+
elif path.endswith(".json"):
|
|
417
|
+
format = "json"
|
|
418
|
+
else:
|
|
419
|
+
raise ValueError(
|
|
420
|
+
f"Cannot determine format from path '{path}'. "
|
|
421
|
+
"Use a file extension (.csv, .parquet, .json) or specify format explicitly."
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
if format not in ("csv", "parquet", "json"):
|
|
425
|
+
raise ValueError(f"format must be 'csv', 'parquet', or 'json', got '{format}'")
|
|
426
|
+
|
|
427
|
+
params = self._build_params()
|
|
428
|
+
|
|
429
|
+
if format == "json":
|
|
430
|
+
# For JSON, fetch as dict and write manually
|
|
431
|
+
import json
|
|
432
|
+
results = await self.as_dict()
|
|
433
|
+
with open(path, "w") as f:
|
|
434
|
+
json.dump(results, f, indent=2)
|
|
435
|
+
else:
|
|
436
|
+
# For CSV and Parquet, use API format parameter
|
|
437
|
+
params["format"] = format
|
|
438
|
+
await self._client._request("GET", self._endpoint, params=params, output_file=path)
|
|
439
|
+
|
|
440
|
+
def __repr__(self) -> str:
|
|
441
|
+
return f"AsyncQueryBuilder(endpoint={self._endpoint!r}, params={self._params!r}, verbose={self._verbose})"
|