- Fix 71 invalid-syntax files (class-body newline-broken assignments) - Add from/None chain to 307 B904 raise-without-from sites - Add B008 ignore to ruff.toml (already in pyproject.toml) - Noqa F401 on __init__.py re-exports (137 sites) - Noqa E402 on deferred imports (63 sites) - Bulk-add stdlib/FastAPI/project imports for F821 (127 sites) - Replace ×→x, –→-, …→... in docstrings (4093 chars) - Manual refactor of 5 SIM103/SIM116 patterns Tests: 791 passed (66 deselected due to pre-existing Redis issues in test_rag.py) Co-authored-by: opencode <opencode@rugmunch.io>
98 lines
3.6 KiB
Python
98 lines
3.6 KiB
Python
"""
|
|
X402 Tool Data Provider - Cached, rate-limited data access for all x402 tools.
|
|
|
|
Replace raw aiohttp/httpx calls with this provider. One import, everything cached.
|
|
|
|
Usage in x402 routers:
|
|
from app.caching_shield.tool_data import td
|
|
|
|
# Instead of: async with aiohttp.ClientSession() as s: r = await s.get(url)
|
|
# Use: result = await td.token_price(mint="So111...")
|
|
# Returns: {"price_usd": 79.5, "source": "jupiter", "cached": False}
|
|
"""
|
|
|
|
from app.caching_shield.unified_layer import get_data_layer
|
|
|
|
|
|
class ToolData:
|
|
"""Cached, rate-limited data provider for x402 tool routers."""
|
|
|
|
def __init__(self):
|
|
self._layer = get_data_layer()
|
|
|
|
async def token_price(self, mint: str) -> dict:
|
|
r = await self._layer.fetch("token_price", mint=mint)
|
|
return r.to_dict() if r else {"error": "no data"}
|
|
|
|
async def token_meta(self, mint: str) -> dict:
|
|
r = await self._layer.fetch("token_meta", mint=mint)
|
|
return r.to_dict() if r else {"error": "no data"}
|
|
|
|
async def wallet_balance(self, address: str) -> dict:
|
|
r = await self._layer.fetch("wallet_balance", address=address)
|
|
return r.to_dict() if r else {"error": "no data"}
|
|
|
|
async def risk_scan(self, address: str, chain: str = "solana") -> dict:
|
|
r = await self._layer.fetch("risk_scan", address=address, chain=chain)
|
|
return r.to_dict() if r else {"error": "no data"}
|
|
|
|
async def tx_history(self, address: str) -> dict:
|
|
r = await self._layer.fetch("tx_history", address=address)
|
|
return r.to_dict() if r else {"error": "no data"}
|
|
|
|
async def funding_source(self, address: str, chain_id: int = 1) -> dict:
|
|
r = await self._layer.fetch("funding_source", address=address, chain_id=chain_id)
|
|
return r.to_dict() if r else {"error": "no data"}
|
|
|
|
async def call_tool(self, tool_id: str, params: dict | None = None) -> dict:
|
|
"""Generic tool dispatcher - calls unified_layer.fetch with tool_id and params.
|
|
|
|
This is the primary method for trial execution and MCP tool calls.
|
|
Falls back to specific ToolData methods for known tools, or uses
|
|
the unified layer's generic fetch for all 127 tools.
|
|
|
|
Returns None if the tool execution fails (so middleware can fall back
|
|
to POST route handlers).
|
|
"""
|
|
params = params or {}
|
|
|
|
# Fallback: try specific methods for common tools
|
|
method_map = {
|
|
"risk_scan": self.risk_scan,
|
|
"token_price": self.token_price,
|
|
"token_meta": self.token_meta,
|
|
"wallet_balance": self.wallet_balance,
|
|
"tx_history": self.tx_history,
|
|
"funding_source": self.funding_source,
|
|
}
|
|
if tool_id in method_map:
|
|
try:
|
|
result = await method_map[tool_id](**params)
|
|
# If the result has only an error key, it's not real data - return None
|
|
if isinstance(result, dict) and set(result.keys()) <= {"error"}:
|
|
return None
|
|
return result
|
|
except Exception:
|
|
pass
|
|
|
|
try:
|
|
r = await self._layer.fetch(tool_id, **params)
|
|
if r:
|
|
result = r.to_dict()
|
|
result["tool"] = tool_id
|
|
# If result has only an error key, it's not real data - return None
|
|
if set(result.keys()) <= {"error", "tool"}:
|
|
return None
|
|
return result
|
|
except Exception:
|
|
pass
|
|
|
|
# Tool not available via DataBus - return None so middleware falls back
|
|
return None
|
|
|
|
def stats(self) -> dict:
|
|
return self._layer.stats()
|
|
|
|
|
|
# Singleton
|
|
td = ToolData()
|