defistream 1.1.0__tar.gz → 1.1.1__tar.gz
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-1.1.0 → defistream-1.1.1}/.claude/settings.local.json +6 -1
- {defistream-1.1.0 → defistream-1.1.1}/PKG-INFO +30 -4
- {defistream-1.1.0 → defistream-1.1.1}/README.md +29 -3
- {defistream-1.1.0 → defistream-1.1.1}/pyproject.toml +1 -1
- {defistream-1.1.0 → defistream-1.1.1}/src/defistream/client.py +4 -0
- defistream-1.1.1/tests/conftest.py +117 -0
- defistream-1.1.1/tests/test_integration.py +1484 -0
- {defistream-1.1.0 → defistream-1.1.1}/.gitignore +0 -0
- {defistream-1.1.0 → defistream-1.1.1}/LICENSE +0 -0
- {defistream-1.1.0 → defistream-1.1.1}/src/defistream/__init__.py +0 -0
- {defistream-1.1.0 → defistream-1.1.1}/src/defistream/exceptions.py +0 -0
- {defistream-1.1.0 → defistream-1.1.1}/src/defistream/models.py +0 -0
- {defistream-1.1.0 → defistream-1.1.1}/src/defistream/protocols.py +0 -0
- {defistream-1.1.0 → defistream-1.1.1}/src/defistream/py.typed +0 -0
- {defistream-1.1.0 → defistream-1.1.1}/src/defistream/query.py +0 -0
- {defistream-1.1.0 → defistream-1.1.1}/tests/__init__.py +0 -0
- {defistream-1.1.0 → defistream-1.1.1}/tests/test_client.py +0 -0
|
@@ -10,7 +10,12 @@
|
|
|
10
10
|
"Bash(python3:*)",
|
|
11
11
|
"Bash(pip3 install:*)",
|
|
12
12
|
"Bash(/home/mvp/Running/Horatio_Chain_Scan/DeFiStream/python-client/.venv/bin/pip install:*)",
|
|
13
|
-
"Bash(/home/mvp/Running/Horatio_Chain_Scan/DeFiStream/python-client/.venv/bin/python:*)"
|
|
13
|
+
"Bash(/home/mvp/Running/Horatio_Chain_Scan/DeFiStream/python-client/.venv/bin/python:*)",
|
|
14
|
+
"Bash(source:*)",
|
|
15
|
+
"Bash(TWINE_USERNAME=__token__ TWINE_PASSWORD=\"$PYPI_PASSWORD\" /home/mvp/Running/Horatio_Chain_Scan/DeFiStream/python-client/.venv/bin/python:*)",
|
|
16
|
+
"Bash(git commit -m \"$\\(cat <<''EOF''\nBump python-client version to 1.1.0\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
|
|
17
|
+
"Bash(git push:*)",
|
|
18
|
+
"Bash(git subtree push:*)"
|
|
14
19
|
]
|
|
15
20
|
}
|
|
16
21
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: defistream
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.1
|
|
4
4
|
Summary: Python client for the DeFiStream API
|
|
5
5
|
Project-URL: Homepage, https://defistream.dev
|
|
6
6
|
Project-URL: Documentation, https://docs.defistream.dev
|
|
@@ -336,6 +336,21 @@ for transfer in transfers:
|
|
|
336
336
|
|
|
337
337
|
> **Note:** `as_dict()` and `as_file("*.json")` use JSON format which has a **10,000 block limit**. For larger block ranges, use `as_df()` or `as_file()` with `.parquet` or `.csv` extensions, which support up to 1,000,000 blocks.
|
|
338
338
|
|
|
339
|
+
### Context Manager
|
|
340
|
+
|
|
341
|
+
Both sync and async clients support context managers to automatically close connections:
|
|
342
|
+
|
|
343
|
+
```python
|
|
344
|
+
# Sync
|
|
345
|
+
with DeFiStream() as client:
|
|
346
|
+
df = (
|
|
347
|
+
client.erc20.transfers("USDT")
|
|
348
|
+
.network("ETH")
|
|
349
|
+
.block_range(21000000, 21010000)
|
|
350
|
+
.as_df()
|
|
351
|
+
)
|
|
352
|
+
```
|
|
353
|
+
|
|
339
354
|
### Async Usage
|
|
340
355
|
|
|
341
356
|
```python
|
|
@@ -355,6 +370,14 @@ async def main():
|
|
|
355
370
|
asyncio.run(main())
|
|
356
371
|
```
|
|
357
372
|
|
|
373
|
+
### List Available Decoders
|
|
374
|
+
|
|
375
|
+
```python
|
|
376
|
+
client = DeFiStream()
|
|
377
|
+
decoders = client.decoders()
|
|
378
|
+
print(decoders) # ['native_token', 'erc20', 'aave', 'uniswap', 'lido', 'stader', 'threshold']
|
|
379
|
+
```
|
|
380
|
+
|
|
358
381
|
## Configuration
|
|
359
382
|
|
|
360
383
|
### Environment Variables
|
|
@@ -455,8 +478,11 @@ print(f"Request cost: {client.last_response.request_cost}")
|
|
|
455
478
|
| Method | Protocols | Description |
|
|
456
479
|
|--------|-----------|-------------|
|
|
457
480
|
| `.token(symbol)` | ERC20 | Token symbol (USDT, USDC) or contract address (required) |
|
|
458
|
-
| `.sender(
|
|
459
|
-
| `.receiver(
|
|
481
|
+
| `.sender(*addrs)` | ERC20, Native | Filter by sender address (multi-value) |
|
|
482
|
+
| `.receiver(*addrs)` | ERC20, Native | Filter by receiver address (multi-value) |
|
|
483
|
+
| `.involving(*addrs)` | All | Filter by any involved address (multi-value) |
|
|
484
|
+
| `.from_address(*addrs)` | ERC20, Native | Alias for `.sender()` |
|
|
485
|
+
| `.to_address(*addrs)` | ERC20, Native | Alias for `.receiver()` |
|
|
460
486
|
| `.min_amount(amt)` | ERC20, Native | Minimum transfer amount |
|
|
461
487
|
| `.max_amount(amt)` | ERC20, Native | Maximum transfer amount |
|
|
462
488
|
| `.eth_market_type(type)` | AAVE | Market type for ETH: 'Core', 'Prime', 'EtherFi' |
|
|
@@ -477,7 +503,7 @@ Filter events by entity names or categories using the labels database. Available
|
|
|
477
503
|
| `.receiver_label(label)` | ERC20, Native | Filter receiver by label substring |
|
|
478
504
|
| `.receiver_category(cat)` | ERC20, Native | Filter receiver by category |
|
|
479
505
|
|
|
480
|
-
**Multi-value support:**
|
|
506
|
+
**Multi-value support:** Pass multiple values as separate arguments (e.g., `.sender_label("Binance", "Coinbase")`) or as a comma-separated string (e.g., `.sender_label("Binance,Coinbase")`). Both forms are equivalent.
|
|
481
507
|
|
|
482
508
|
**Mutual exclusivity:** Within each slot (involving/sender/receiver), only one of address/label/category can be set. `involving*` filters cannot be combined with `sender*`/`receiver*` filters.
|
|
483
509
|
|
|
@@ -302,6 +302,21 @@ for transfer in transfers:
|
|
|
302
302
|
|
|
303
303
|
> **Note:** `as_dict()` and `as_file("*.json")` use JSON format which has a **10,000 block limit**. For larger block ranges, use `as_df()` or `as_file()` with `.parquet` or `.csv` extensions, which support up to 1,000,000 blocks.
|
|
304
304
|
|
|
305
|
+
### Context Manager
|
|
306
|
+
|
|
307
|
+
Both sync and async clients support context managers to automatically close connections:
|
|
308
|
+
|
|
309
|
+
```python
|
|
310
|
+
# Sync
|
|
311
|
+
with DeFiStream() as client:
|
|
312
|
+
df = (
|
|
313
|
+
client.erc20.transfers("USDT")
|
|
314
|
+
.network("ETH")
|
|
315
|
+
.block_range(21000000, 21010000)
|
|
316
|
+
.as_df()
|
|
317
|
+
)
|
|
318
|
+
```
|
|
319
|
+
|
|
305
320
|
### Async Usage
|
|
306
321
|
|
|
307
322
|
```python
|
|
@@ -321,6 +336,14 @@ async def main():
|
|
|
321
336
|
asyncio.run(main())
|
|
322
337
|
```
|
|
323
338
|
|
|
339
|
+
### List Available Decoders
|
|
340
|
+
|
|
341
|
+
```python
|
|
342
|
+
client = DeFiStream()
|
|
343
|
+
decoders = client.decoders()
|
|
344
|
+
print(decoders) # ['native_token', 'erc20', 'aave', 'uniswap', 'lido', 'stader', 'threshold']
|
|
345
|
+
```
|
|
346
|
+
|
|
324
347
|
## Configuration
|
|
325
348
|
|
|
326
349
|
### Environment Variables
|
|
@@ -421,8 +444,11 @@ print(f"Request cost: {client.last_response.request_cost}")
|
|
|
421
444
|
| Method | Protocols | Description |
|
|
422
445
|
|--------|-----------|-------------|
|
|
423
446
|
| `.token(symbol)` | ERC20 | Token symbol (USDT, USDC) or contract address (required) |
|
|
424
|
-
| `.sender(
|
|
425
|
-
| `.receiver(
|
|
447
|
+
| `.sender(*addrs)` | ERC20, Native | Filter by sender address (multi-value) |
|
|
448
|
+
| `.receiver(*addrs)` | ERC20, Native | Filter by receiver address (multi-value) |
|
|
449
|
+
| `.involving(*addrs)` | All | Filter by any involved address (multi-value) |
|
|
450
|
+
| `.from_address(*addrs)` | ERC20, Native | Alias for `.sender()` |
|
|
451
|
+
| `.to_address(*addrs)` | ERC20, Native | Alias for `.receiver()` |
|
|
426
452
|
| `.min_amount(amt)` | ERC20, Native | Minimum transfer amount |
|
|
427
453
|
| `.max_amount(amt)` | ERC20, Native | Maximum transfer amount |
|
|
428
454
|
| `.eth_market_type(type)` | AAVE | Market type for ETH: 'Core', 'Prime', 'EtherFi' |
|
|
@@ -443,7 +469,7 @@ Filter events by entity names or categories using the labels database. Available
|
|
|
443
469
|
| `.receiver_label(label)` | ERC20, Native | Filter receiver by label substring |
|
|
444
470
|
| `.receiver_category(cat)` | ERC20, Native | Filter receiver by category |
|
|
445
471
|
|
|
446
|
-
**Multi-value support:**
|
|
472
|
+
**Multi-value support:** Pass multiple values as separate arguments (e.g., `.sender_label("Binance", "Coinbase")`) or as a comma-separated string (e.g., `.sender_label("Binance,Coinbase")`). Both forms are equivalent.
|
|
447
473
|
|
|
448
474
|
**Mutual exclusivity:** Within each slot (involving/sender/receiver), only one of address/label/category can be set. `involving*` filters cannot be combined with `sender*`/`receiver*` filters.
|
|
449
475
|
|
|
@@ -289,6 +289,8 @@ class DeFiStream(BaseClient):
|
|
|
289
289
|
if response.status_code >= 400:
|
|
290
290
|
self._handle_error_response(response)
|
|
291
291
|
data = response.json()
|
|
292
|
+
if isinstance(data, list):
|
|
293
|
+
return data
|
|
292
294
|
return data.get("decoders", [])
|
|
293
295
|
|
|
294
296
|
|
|
@@ -377,4 +379,6 @@ class AsyncDeFiStream(BaseClient):
|
|
|
377
379
|
if response.status_code >= 400:
|
|
378
380
|
self._handle_error_response(response)
|
|
379
381
|
data = response.json()
|
|
382
|
+
if isinstance(data, list):
|
|
383
|
+
return data
|
|
380
384
|
return data.get("decoders", [])
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared fixtures for DeFiStream python-client integration tests.
|
|
3
|
+
|
|
4
|
+
Mirrors the API test suite conftest at api/tests/api-gateway/decoders/conftest.py,
|
|
5
|
+
adapted for the python-client's builder-pattern interface.
|
|
6
|
+
|
|
7
|
+
Run with:
|
|
8
|
+
python -m pytest tests/test_integration.py -v
|
|
9
|
+
python -m pytest tests/test_integration.py -v --local # local dev gateway
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
import pytest
|
|
17
|
+
|
|
18
|
+
from defistream import AsyncDeFiStream, DeFiStream
|
|
19
|
+
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
# --local flag support
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
_local_mode = "--local" in sys.argv
|
|
25
|
+
|
|
26
|
+
_PRODUCTION_URL = "https://api.defistream.dev/v1"
|
|
27
|
+
_LOCAL_URL = "http://localhost:8081/v1"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def pytest_addoption(parser):
|
|
31
|
+
parser.addoption(
|
|
32
|
+
"--local",
|
|
33
|
+
action="store_true",
|
|
34
|
+
default=False,
|
|
35
|
+
help="Test against local dev gateway (http://localhost:8081/v1) instead of production",
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# ---------------------------------------------------------------------------
|
|
40
|
+
# .env loading
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
def _load_env_file(path: Path) -> dict[str, str]:
|
|
44
|
+
if not path.exists():
|
|
45
|
+
return {}
|
|
46
|
+
env: dict[str, str] = {}
|
|
47
|
+
for line in path.read_text(encoding="utf-8").splitlines():
|
|
48
|
+
line = line.strip()
|
|
49
|
+
if not line or line.startswith("#") or "=" not in line:
|
|
50
|
+
continue
|
|
51
|
+
key, value = line.split("=", 1)
|
|
52
|
+
key = key.strip()
|
|
53
|
+
value = value.strip()
|
|
54
|
+
if (value.startswith('"') and value.endswith('"')) or (
|
|
55
|
+
value.startswith("'") and value.endswith("'")
|
|
56
|
+
):
|
|
57
|
+
value = value[1:-1]
|
|
58
|
+
env[key] = value
|
|
59
|
+
return env
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _get_env() -> dict[str, str]:
|
|
63
|
+
env = dict(os.environ)
|
|
64
|
+
project_root = Path(__file__).resolve().parents[1] # python-client/
|
|
65
|
+
repo_root = project_root.parent # DeFiStream/
|
|
66
|
+
for candidate in [project_root / ".env", repo_root / ".env"]:
|
|
67
|
+
for key, value in _load_env_file(candidate).items():
|
|
68
|
+
if key not in env:
|
|
69
|
+
env[key] = value
|
|
70
|
+
return env
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
_env = _get_env()
|
|
74
|
+
|
|
75
|
+
# ---------------------------------------------------------------------------
|
|
76
|
+
# Resolved configuration
|
|
77
|
+
# ---------------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
API_BASE_URL = _LOCAL_URL if _local_mode else _env.get("API_BASE_URL", _PRODUCTION_URL).rstrip("/")
|
|
80
|
+
TEST_API_KEY = _env.get("TEST_API_KEY", "")
|
|
81
|
+
|
|
82
|
+
# ---------------------------------------------------------------------------
|
|
83
|
+
# Auto-skip integration tests when API key is missing
|
|
84
|
+
# ---------------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
_skip_no_key = pytest.mark.skipif(
|
|
87
|
+
not TEST_API_KEY,
|
|
88
|
+
reason="TEST_API_KEY not set in environment or .env",
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def pytest_collection_modifyitems(config, items):
|
|
93
|
+
"""Skip integration tests automatically when TEST_API_KEY is absent."""
|
|
94
|
+
for item in items:
|
|
95
|
+
if "test_integration" in str(item.fspath):
|
|
96
|
+
item.add_marker(_skip_no_key)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# ---------------------------------------------------------------------------
|
|
100
|
+
# Fixtures
|
|
101
|
+
# ---------------------------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@pytest.fixture(scope="module")
|
|
105
|
+
def client():
|
|
106
|
+
"""Sync DeFiStream client shared across a test module."""
|
|
107
|
+
c = DeFiStream(api_key=TEST_API_KEY, base_url=API_BASE_URL)
|
|
108
|
+
yield c
|
|
109
|
+
c.close()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@pytest.fixture
|
|
113
|
+
async def async_client():
|
|
114
|
+
"""Async DeFiStream client — function-scoped for a clean event loop."""
|
|
115
|
+
c = AsyncDeFiStream(api_key=TEST_API_KEY, base_url=API_BASE_URL)
|
|
116
|
+
yield c
|
|
117
|
+
await c.close()
|