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.
@@ -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.0
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(addr)` | ERC20, Native | Filter by sender address |
459
- | `.receiver(addr)` | ERC20, Native | Filter by receiver address |
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:** Comma-separated values are accepted (e.g., `"Binance,Coinbase"`).
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(addr)` | ERC20, Native | Filter by sender address |
425
- | `.receiver(addr)` | ERC20, Native | Filter by receiver address |
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:** Comma-separated values are accepted (e.g., `"Binance,Coinbase"`).
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
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "defistream"
7
- version = "1.1.0"
7
+ version = "1.1.1"
8
8
  description = "Python client for the DeFiStream API"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -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()