From 37be27e318c1b5324d7cc9fe5b91b5fc93acc874 Mon Sep 17 00:00:00 2001 From: Martin Kersner Date: Wed, 1 Jul 2026 11:28:05 +0900 Subject: [PATCH] Consume codegen endpoint registry; pilot on CexCandle Adds datamaxi/_endpoints.py (generated by datamaxi-codegen from the backend OpenAPI spec) as the single source of truth for endpoint paths, methods, path/query split, required params, and defaults. - API.request_endpoint(op_id, **params): looks up the registry, fills defaults, enforces required params, interpolates path params, sends. - CexCandle refactored onto it (paths/param assembly removed); signatures, docstrings, semantic checks, and DataFrame conversion unchanged. - _endpoints.py is generated: excluded from black/flake8. Public interface unchanged; existing mocked tests pass unmodified. --- datamaxi/_endpoints.py | 1537 +++++++++++++++++++++++++++++++ datamaxi/api.py | 49 + datamaxi/datamaxi/cex_candle.py | 38 +- pyproject.toml | 4 + setup.cfg | 3 +- 5 files changed, 1606 insertions(+), 25 deletions(-) create mode 100644 datamaxi/_endpoints.py diff --git a/datamaxi/_endpoints.py b/datamaxi/_endpoints.py new file mode 100644 index 0000000..0d1bc4e --- /dev/null +++ b/datamaxi/_endpoints.py @@ -0,0 +1,1537 @@ +""" +Auto-generated endpoint registry from openapi.yaml. +DO NOT EDIT — regenerate with: make python +""" + +ENDPOINTS = { + "cex_announcements": { + "path": "/api/v1/cex/announcements", + "method": "GET", + "tag": "announcements", + "summary": "Announcements", + "requires_auth": True, + "group": "announcements", + "params": { + "page": { + "required": False, + "in": "query", + "type": "int", + "default": 1, + "description": "Page number", + }, + "limit": { + "required": False, + "in": "query", + "type": "int", + "default": 10, + "description": "Page size", + }, + "sort": { + "required": False, + "in": "query", + "type": "str", + "default": "desc", + "enum": ['asc', 'desc'], + "description": "Specifies sort", + }, + "key": { + "required": False, + "in": "query", + "type": "str", + "default": "timestamp", + "enum": ['exchange', 'category', 'title', 'timestamp'], + "description": "Specifies key to sort by", + }, + "exchange": { + "required": False, + "in": "query", + "type": "str", + "description": "Specifies exchange(s), separated by ,", + }, + "category": { + "required": False, + "in": "query", + "type": "str", + "default": "", + "enum": ['notice', 'listing', 'delisting', 'user_events'], + "description": "Specifies category(s), separated by ,", + }, + }, + }, + "cex_candle": { + "path": "/api/v1/cex/candle", + "method": "GET", + "tag": "cex-candle", + "summary": "Data", + "requires_auth": True, + "group": "cex", + "subgroup": "candle", + "params": { + "exchange": { + "required": True, + "in": "query", + "type": "str", + "description": "Specifes exchange", + }, + "market": { + "required": True, + "in": "query", + "type": "str", + "default": "spot", + "enum": ['spot', 'futures'], + "description": "Specifies market", + }, + "symbol": { + "required": True, + "in": "query", + "type": "str", + "description": "Specifies symbol", + }, + "currency": { + "required": False, + "in": "query", + "type": "str", + "default": "USD", + "enum": ['USD', 'KRW'], + "description": "Specifies currency", + }, + "interval": { + "required": False, + "in": "query", + "type": "str", + "default": "1d", + "description": "Specifies interval", + }, + "from": { + "required": False, + "in": "query", + "type": "str", + "description": "Specifies from", + }, + "to": { + "required": False, + "in": "query", + "type": "str", + "description": "Specifies to", + }, + }, + }, + "cex_candle_exchanges": { + "path": "/api/v1/cex/candle/exchanges", + "method": "GET", + "tag": "cex-candle", + "summary": "Exchanges", + "requires_auth": False, + "group": "cex", + "subgroup": "candle", + "params": { + "market": { + "required": True, + "in": "query", + "type": "str", + "enum": ['spot', 'futures'], + "description": "Specifies market type", + }, + }, + }, + "cex_candle_intervals": { + "path": "/api/v1/cex/candle/intervals", + "method": "GET", + "tag": "cex-candle", + "summary": "Intervals", + "requires_auth": False, + "group": "cex", + "subgroup": "candle", + "params": { + }, + }, + "cex_candle_symbols": { + "path": "/api/v1/cex/candle/symbols", + "method": "GET", + "tag": "cex-candle", + "summary": "Symbols", + "requires_auth": False, + "group": "cex", + "subgroup": "candle", + "params": { + "exchange": { + "required": False, + "in": "query", + "type": "str", + "description": "Specifies exchange name", + }, + "market": { + "required": False, + "in": "query", + "type": "str", + "enum": ['spot', 'futures'], + "description": "Specifies market type", + }, + }, + }, + "cex_symbol_cautions": { + "path": "/api/v1/cex/symbol/cautions", + "method": "GET", + "tag": "cex-symbol", + "summary": "Active symbol cautions", + "requires_auth": False, + "group": "cex", + "subgroup": "symbol", + "params": { + "exchange": { + "required": False, + "in": "query", + "type": "str", + "description": "Exchange filter (comma-separated, empty = all)", + }, + "market": { + "required": False, + "in": "query", + "type": "str", + "enum": ['spot', 'futures'], + "description": "spot or futures", + }, + "min_level": { + "required": False, + "in": "query", + "type": "str", + "enum": ['caution', 'warning', 'danger'], + "description": "Minimum severity", + }, + "active_only": { + "required": False, + "in": "query", + "type": "bool", + "description": "Exclude rows whose end_at is in the past (default true)", + }, + "limit": { + "required": False, + "in": "query", + "type": "int", + "description": "Page size (default 500, max 5000)", + }, + "page": { + "required": False, + "in": "query", + "type": "int", + "description": "Page number (1-based)", + }, + }, + }, + "cex_symbol_delistings": { + "path": "/api/v1/cex/symbol/delistings", + "method": "GET", + "tag": "cex-symbol", + "summary": "Delisting schedule", + "requires_auth": False, + "group": "cex", + "subgroup": "symbol", + "params": { + "exchange": { + "required": False, + "in": "query", + "type": "str", + "description": "Exchange filter (comma-separated)", + }, + "market": { + "required": False, + "in": "query", + "type": "str", + "enum": ['spot', 'futures'], + "description": "spot or futures", + }, + "from_ms": { + "required": False, + "in": "query", + "type": "int", + "description": "Lower bound for delisting_at (ms epoch, default = now)", + }, + "to_ms": { + "required": False, + "in": "query", + "type": "int", + "description": "Upper bound for delisting_at (ms epoch, default = now+30 days)", + }, + "include_past": { + "required": False, + "in": "query", + "type": "bool", + "description": "Include already-delisted rows (default false)", + }, + "limit": { + "required": False, + "in": "query", + "type": "int", + "description": "Page size (default 200, max 2000)", + }, + "page": { + "required": False, + "in": "query", + "type": "int", + "description": "Page number (1-based)", + }, + }, + }, + "cex_symbol_liquidation": { + "path": "/api/v1/cex/symbol/liquidation", + "method": "GET", + "tag": "cex-symbol", + "summary": "Per-exchange liquidation aggregate for a base asset", + "requires_auth": False, + "group": "cex", + "subgroup": "symbol", + "params": { + "base": { + "required": True, + "in": "query", + "type": "str", + "description": "Base asset (e.g. BTC)", + }, + "window": { + "required": False, + "in": "query", + "type": "str", + "description": "Time window: 1h / 24h / 7d (default 24h, max 30d)", + }, + }, + }, + "cex_symbol_metadata": { + "path": "/api/v1/cex/symbol/metadata", + "method": "GET", + "tag": "cex-symbol", + "summary": "Symbol metadata", + "requires_auth": False, + "group": "cex", + "subgroup": "symbol", + "params": { + "exchange": { + "required": False, + "in": "query", + "type": "str", + "description": "Comma-separated exchange names (empty = all)", + }, + "market": { + "required": False, + "in": "query", + "type": "str", + "enum": ['spot', 'futures'], + "description": "spot or futures (empty = both)", + }, + "base": { + "required": False, + "in": "query", + "type": "str", + "description": "Base asset filter", + }, + "quote": { + "required": False, + "in": "query", + "type": "str", + "description": "Quote asset filter", + }, + "status": { + "required": False, + "in": "query", + "type": "str", + "description": "trading_status filter (repeatable, comma-separated)", + }, + "limit": { + "required": False, + "in": "query", + "type": "int", + "description": "Page size (default 200, max 2000)", + }, + "page": { + "required": False, + "in": "query", + "type": "int", + "description": "Page number (1-based)", + }, + }, + }, + "cex_symbol_oi": { + "path": "/api/v1/cex/symbol/oi", + "method": "GET", + "tag": "cex-symbol", + "summary": "Per-exchange Open Interest for a base asset", + "requires_auth": False, + "group": "cex", + "subgroup": "symbol", + "params": { + "base": { + "required": True, + "in": "query", + "type": "str", + "description": "Base asset (e.g. BTC)", + }, + "exchange": { + "required": False, + "in": "query", + "type": "str", + "description": "Exchange filter (narrows the Redis scan)", + }, + }, + }, + "cex_symbol_oi_stats": { + "path": "/api/v1/cex/symbol/oi-stats", + "method": "GET", + "tag": "cex-symbol", + "summary": "Per-exchange Open Interest snapshot with deltas", + "requires_auth": False, + "group": "cex", + "subgroup": "symbol", + "params": { + "base": { + "required": True, + "in": "query", + "type": "str", + "description": "Base asset (e.g. BTC)", + }, + "exchange": { + "required": False, + "in": "query", + "type": "str", + "description": "Exchange filter — when omitted, returns every venue carrying the base", + }, + "currency": { + "required": False, + "in": "query", + "type": "str", + "default": "USD", + "enum": ['USD', 'KRW'], + "description": "Convert *_usd fields to target currency (USD or KRW)", + }, + }, + }, + "cex_symbol_tags": { + "path": "/api/v1/cex/symbol/tags", + "method": "GET", + "tag": "cex-symbol", + "summary": "Symbol tags", + "requires_auth": False, + "group": "cex", + "subgroup": "symbol", + "params": { + "tag": { + "required": False, + "in": "query", + "type": "str", + "description": "Tag filter (repeatable, comma-separated)", + }, + "exchange": { + "required": False, + "in": "query", + "type": "str", + "description": "Exchange filter (repeatable, comma-separated)", + }, + "market": { + "required": False, + "in": "query", + "type": "str", + "enum": ['spot', 'futures'], + "description": "spot or futures", + }, + "base": { + "required": False, + "in": "query", + "type": "str", + "description": "Base asset filter", + }, + "source": { + "required": False, + "in": "query", + "type": "str", + "enum": ['rest_native', 'announcement', 'cmc', 'manual'], + "description": "Tag source filter", + }, + "min_confidence": { + "required": False, + "in": "query", + "type": "int", + "description": "Minimum confidence (0-100, default 80)", + }, + "limit": { + "required": False, + "in": "query", + "type": "int", + "description": "Page size (default 500, max 5000)", + }, + "page": { + "required": False, + "in": "query", + "type": "int", + "description": "Page number (1-based)", + }, + }, + }, + "cex_symbol_volume": { + "path": "/api/v1/cex/symbol/volume", + "method": "GET", + "tag": "cex-symbol", + "summary": "Per-exchange 24h volume", + "requires_auth": False, + "group": "cex", + "subgroup": "symbol", + "params": { + "base": { + "required": True, + "in": "query", + "type": "str", + "description": "Base asset (e.g. BTC)", + }, + "market": { + "required": False, + "in": "query", + "type": "str", + "enum": ['spot', 'futures'], + "description": "Filter to spot or futures", + }, + }, + }, + "forex": { + "path": "/api/v1/forex", + "method": "GET", + "tag": "forex", + "summary": "Forex", + "requires_auth": True, + "group": "forex", + "params": { + "symbol": { + "required": False, + "in": "query", + "type": "str", + "description": "Specifies symbol", + }, + }, + }, + "forex_symbols": { + "path": "/api/v1/forex/symbols", + "method": "GET", + "tag": "forex", + "summary": "Symbols", + "requires_auth": False, + "group": "forex", + "params": { + }, + }, + "funding_rate_exchanges": { + "path": "/api/v1/funding-rate/exchanges", + "method": "GET", + "tag": "funding-rate", + "summary": "Exchanges", + "requires_auth": False, + "group": "funding_rate", + "params": { + }, + }, + "funding_rate_history": { + "path": "/api/v1/funding-rate/history", + "method": "GET", + "tag": "funding-rate", + "summary": "Historical funding rate", + "requires_auth": True, + "group": "funding_rate", + "params": { + "exchange": { + "required": True, + "in": "query", + "type": "str", + "description": "Specifes exchange", + }, + "symbol": { + "required": True, + "in": "query", + "type": "str", + "description": "Specifies symbol", + }, + "page": { + "required": False, + "in": "query", + "type": "str", + "default": "1", + "description": "Specifies page", + }, + "limit": { + "required": False, + "in": "query", + "type": "str", + "default": "1000", + "description": "Specifies limit", + }, + "from": { + "required": False, + "in": "query", + "type": "str", + "description": "Specifies from", + }, + "to": { + "required": False, + "in": "query", + "type": "str", + "description": "Specifies to", + }, + "sort": { + "required": False, + "in": "query", + "type": "str", + "default": "asc", + "enum": ['asc', 'desc'], + "description": "Specifies sort", + }, + }, + }, + "funding_rate_latest": { + "path": "/api/v1/funding-rate/latest", + "method": "GET", + "tag": "funding-rate", + "summary": "Latest funding rate", + "requires_auth": True, + "group": "funding_rate", + "params": { + "exchange": { + "required": True, + "in": "query", + "type": "str", + "description": "Specifies exchange", + }, + "symbol": { + "required": True, + "in": "query", + "type": "str", + "description": "Specifies symbol", + }, + }, + }, + "funding_rate_symbols": { + "path": "/api/v1/funding-rate/symbols", + "method": "GET", + "tag": "funding-rate", + "summary": "Symbols", + "requires_auth": False, + "group": "funding_rate", + "params": { + "exchange": { + "required": False, + "in": "query", + "type": "str", + "description": "Specifies exchange name. Omit to receive symbols for all exchanges; constrain to a single exchange when filtering.", + }, + }, + }, + "index_price": { + "path": "/api/v1/index-price", + "method": "GET", + "tag": "index-price", + "summary": "Historical Index Price", + "requires_auth": True, + "group": "index_price", + "params": { + "asset": { + "required": True, + "in": "query", + "type": "str", + "description": "Asset", + }, + "from": { + "required": False, + "in": "query", + "type": "str", + "default": "now - 1 month", + "description": "Specifies from", + }, + "to": { + "required": False, + "in": "query", + "type": "str", + "default": "now", + "description": "Specifies to", + }, + "interval": { + "required": False, + "in": "query", + "type": "str", + "default": "5m", + "description": "interval", + }, + }, + }, + "liquidation": { + "path": "/api/v1/liquidation", + "method": "GET", + "tag": "liquidation", + "summary": "Recent Liquidations", + "requires_auth": True, + "group": "liquidation", + "params": { + "exchange": { + "required": True, + "in": "query", + "type": "str", + "description": "Exchange identifier", + }, + "symbol": { + "required": True, + "in": "query", + "type": "str", + "description": "Exchange-native API symbol", + }, + "limit": { + "required": False, + "in": "query", + "type": "int", + "default": 100, + "description": "Number of events to return (1-1000)", + }, + }, + }, + "liquidation_feed": { + "path": "/api/v1/liquidation/feed", + "method": "GET", + "tag": "liquidation", + "summary": "Liquidation feed", + "requires_auth": True, + "group": "liquidation", + "params": { + "exchange": { + "required": False, + "in": "query", + "type": "str", + "description": "Exchange filter", + }, + "base": { + "required": False, + "in": "query", + "type": "str", + "description": "Base asset filter (case-insensitive)", + }, + "min_volume_usd": { + "required": False, + "in": "query", + "type": "float", + "description": "Minimum VolumeUsd filter", + }, + "limit": { + "required": False, + "in": "query", + "type": "int", + "default": 100, + "description": "Number of events (1-1000)", + }, + }, + }, + "liquidation_heatmap": { + "path": "/api/v1/liquidation/heatmap", + "method": "GET", + "tag": "liquidation", + "summary": "Liquidation heatmap (token × exchange)", + "requires_auth": True, + "group": "liquidation", + "params": { + "window": { + "required": False, + "in": "query", + "type": "str", + "default": "1h", + "enum": ['1h', '4h', '24h'], + "description": "Rolling window", + }, + "top_n": { + "required": False, + "in": "query", + "type": "int", + "default": 10, + "description": "Top N tokens by total", + }, + }, + }, + "liquidation_map": { + "path": "/api/v1/liquidation/map", + "method": "GET", + "tag": "liquidation", + "summary": "Liquidation map (price × leverage tier)", + "requires_auth": True, + "group": "liquidation", + "params": { + "exchange": { + "required": False, + "in": "query", + "type": "str", + "default": "binance", + "description": "Exchange", + }, + "base": { + "required": True, + "in": "query", + "type": "str", + "description": "Base asset", + }, + "quote": { + "required": False, + "in": "query", + "type": "str", + "default": "USDT", + "description": "Quote asset", + }, + }, + }, + "liquidation_stats": { + "path": "/api/v1/liquidation/stats", + "method": "GET", + "tag": "liquidation", + "summary": "Liquidation KPI stats", + "requires_auth": True, + "group": "liquidation", + "params": { + "window": { + "required": False, + "in": "query", + "type": "str", + "default": "1h", + "enum": ['1h', '4h', '24h'], + "description": "Rolling window", + }, + "exchange": { + "required": False, + "in": "query", + "type": "str", + "description": "Exchange filter", + }, + "min_volume_usd": { + "required": False, + "in": "query", + "type": "float", + "description": "Minimum VolumeUsd filter", + }, + }, + }, + "liquidation_symbol_history": { + "path": "/api/v1/liquidation/symbol-history", + "method": "GET", + "tag": "liquidation", + "summary": "Liquidation history (time series for one symbol)", + "requires_auth": True, + "group": "liquidation", + "params": { + "symbol": { + "required": True, + "in": "query", + "type": "str", + "description": "Base asset", + }, + "quote": { + "required": False, + "in": "query", + "type": "str", + "default": "USDT", + "description": "Quote asset", + }, + "exchange": { + "required": False, + "in": "query", + "type": "str", + "description": "Optional exchange filter for the liquidation aggregation. The price line stays on Binance unless this is set.", + }, + "interval": { + "required": False, + "in": "query", + "type": "str", + "default": "5m", + "enum": ['5m', '15m', '1h'], + "description": "Bucket interval", + }, + "window": { + "required": False, + "in": "query", + "type": "str", + "default": "24h", + "enum": ['24h', '72h', '7d'], + "description": "Lookback window", + }, + }, + }, + "listings_historical": { + "path": "/api/v1/listings/historical", + "method": "GET", + "tag": "listing", + "summary": "Historical token listings", + "requires_auth": True, + "group": "listing", + "params": { + "refresh": { + "required": False, + "in": "query", + "type": "bool", + "default": False, + "description": "Refresh cache", + }, + }, + }, + "margin_borrow": { + "path": "/api/v1/margin-borrow", + "method": "GET", + "tag": "margin-borrow", + "summary": "Margin borrow", + "requires_auth": True, + "group": "margin_borrow", + "params": { + "asset": { + "required": True, + "in": "query", + "type": "str", + "description": "Token base asset", + }, + }, + }, + "naver_trend": { + "path": "/api/v1/naver-trend", + "method": "GET", + "tag": "naver-trend", + "summary": "Trend", + "requires_auth": True, + "group": "naver_trend", + "params": { + "symbol": { + "required": True, + "in": "query", + "type": "str", + "description": "Specifies symbol", + }, + }, + }, + "naver_trend_symbols": { + "path": "/api/v1/naver-trend/symbols", + "method": "GET", + "tag": "naver-trend", + "summary": "Symbols", + "requires_auth": True, + "group": "naver_trend", + "params": { + }, + }, + "open_interest": { + "path": "/api/v1/open-interest", + "method": "GET", + "tag": "open-interest", + "summary": "Latest Open Interest", + "requires_auth": True, + "group": "open_interest", + "params": { + "exchange": { + "required": True, + "in": "query", + "type": "str", + "description": "Exchange identifier", + }, + "symbol": { + "required": True, + "in": "query", + "type": "str", + "description": "Exchange-native API symbol", + }, + }, + }, + "open_interest_history_aggregated": { + "path": "/api/v1/open-interest/history-aggregated", + "method": "GET", + "tag": "open-interest", + "summary": "Open Interest history (aggregated)", + "requires_auth": True, + "group": "open_interest", + "params": { + "token_id": { + "required": True, + "in": "query", + "type": "str", + "description": "Token id", + }, + "interval": { + "required": False, + "in": "query", + "type": "str", + "default": "1h", + "enum": ['5m', '15m', '1h', '4h', '1d'], + "description": "Aggregation interval", + }, + "from": { + "required": False, + "in": "query", + "type": "int", + "description": "Start unix-ms (default: depends on interval — 7d for 1h, 30d for 4h, 1y for 1d)", + }, + "to": { + "required": False, + "in": "query", + "type": "int", + "description": "End unix-ms (default: now)", + }, + }, + }, + "open_interest_list": { + "path": "/api/v1/open-interest/list", + "method": "GET", + "tag": "open-interest", + "summary": "Open Interest list", + "requires_auth": True, + "group": "open_interest", + "params": { + "exchange": { + "required": False, + "in": "query", + "type": "str", + "description": "Exchange filter", + }, + }, + }, + "open_interest_overview": { + "path": "/api/v1/open-interest/overview", + "method": "GET", + "tag": "open-interest", + "summary": "Open Interest overview", + "requires_auth": True, + "group": "open_interest", + "params": { + "page": { + "required": False, + "in": "query", + "type": "int", + "default": 1, + "description": "Page", + }, + "limit": { + "required": False, + "in": "query", + "type": "int", + "default": 20, + "description": "Page size", + }, + "key": { + "required": False, + "in": "query", + "type": "str", + "default": "binance", + "description": "Sort-by exchange", + }, + "sort": { + "required": False, + "in": "query", + "type": "str", + "default": "desc", + "enum": ['asc', 'desc'], + "description": "Sort direction", + }, + "query": { + "required": False, + "in": "query", + "type": "str", + "description": "Base symbol search", + }, + }, + }, + "open_interest_summary": { + "path": "/api/v1/open-interest/summary", + "method": "GET", + "tag": "open-interest", + "summary": "Open Interest summary aggregates", + "requires_auth": True, + "group": "open_interest", + "params": { + "top_n": { + "required": False, + "in": "query", + "type": "int", + "default": 10, + "description": "Top N tokens to return", + }, + }, + }, + "front_premium_tags": { + "path": "/api/v1/front/premium/tags", + "method": "GET", + "tag": "premium", + "summary": "Available premium tag filters", + "requires_auth": False, + "group": "premium", + "params": { + }, + }, + "premium": { + "path": "/api/v1/premium", + "method": "GET", + "tag": "premium", + "summary": "Premium", + "requires_auth": True, + "group": "premium", + "params": { + "source_exchange": { + "required": False, + "in": "query", + "type": "str", + "description": "Specifies source exchange(s), separated by ,", + }, + "target_exchange": { + "required": False, + "in": "query", + "type": "str", + "description": "Specifies target exchange(s), separated by ,", + }, + "asset": { + "required": False, + "in": "query", + "type": "str", + "description": "Specifies asset(s), separated by ,", + }, + "source_quote": { + "required": False, + "in": "query", + "type": "str", + "description": "Specifies source quote(s), separated by ,", + }, + "target_quote": { + "required": False, + "in": "query", + "type": "str", + "description": "Specifies target quote(s), separated by ,", + }, + "source_market": { + "required": False, + "in": "query", + "type": "str", + "enum": ['spot', 'futures'], + "description": "Specifies source market", + }, + "target_market": { + "required": False, + "in": "query", + "type": "str", + "enum": ['spot', 'futures'], + "description": "Specifies target market", + }, + "premium_type": { + "required": False, + "in": "query", + "type": "str", + "enum": ['spot-spot', 'futures-futures', 'spot-futures'], + "description": "Specifies premium type(s), separated by ,", + }, + "currency": { + "required": False, + "in": "query", + "type": "str", + "default": "USD", + "description": "Specifies currency applied to price values", + }, + "conversion_base": { + "required": False, + "in": "query", + "type": "str", + "default": "USDT", + "description": "Specifies conversion base", + }, + "page": { + "required": False, + "in": "query", + "type": "int", + "default": 1, + "description": "Page number", + }, + "limit": { + "required": False, + "in": "query", + "type": "int", + "default": 10, + "description": "Page size", + }, + "sort": { + "required": False, + "in": "query", + "type": "str", + "default": "desc", + "enum": ['asc', 'desc'], + "description": "Specifies sort order", + }, + "key": { + "required": False, + "in": "query", + "type": "str", + "default": "pdp", + "description": "Specifies key to sort by", + }, + "query": { + "required": False, + "in": "query", + "type": "str", + "description": "Search query for filtering assets", + }, + "only_transferable": { + "required": False, + "in": "query", + "type": "bool", + "default": False, + "description": "Filter only transferable assets", + }, + "network": { + "required": False, + "in": "query", + "type": "str", + "description": "Specifies network(s), separated by ,", + }, + "min_sv": { + "required": False, + "in": "query", + "type": "float", + "description": "Minimum source volume", + }, + "min_tv": { + "required": False, + "in": "query", + "type": "float", + "description": "Minimum target volume", + }, + }, + }, + "premium_exchanges": { + "path": "/api/v1/premium/exchanges", + "method": "GET", + "tag": "premium", + "summary": "Exchanges", + "requires_auth": False, + "group": "premium", + "params": { + }, + }, + "telegram_channels": { + "path": "/api/v1/telegram/channels", + "method": "GET", + "tag": "telegram", + "summary": "Channels", + "requires_auth": True, + "group": "telegram", + "params": { + "page": { + "required": False, + "in": "query", + "type": "int", + "default": 1, + "description": "Page number", + }, + "limit": { + "required": False, + "in": "query", + "type": "int", + "default": 10, + "description": "Page size", + }, + "category": { + "required": False, + "in": "query", + "type": "str", + "default": "empty", + "description": "Specifies language category of telegram channel", + }, + "key": { + "required": False, + "in": "query", + "type": "str", + "default": "channelName", + "enum": ['channelName', 'handle', 'subscribers', 'createdAt'], + "description": "Specifies key to sort by", + }, + "sort": { + "required": False, + "in": "query", + "type": "str", + "default": "desc", + "enum": ['asc', 'desc'], + "description": "Specifies sort", + }, + }, + }, + "telegram_messages": { + "path": "/api/v1/telegram/messages", + "method": "GET", + "tag": "telegram", + "summary": "Messages", + "requires_auth": True, + "group": "telegram", + "params": { + "channel": { + "required": False, + "in": "query", + "type": "str", + "default": "", + "description": "Specifies channel username", + }, + "page": { + "required": False, + "in": "query", + "type": "int", + "default": 1, + "description": "Page number", + }, + "limit": { + "required": False, + "in": "query", + "type": "int", + "default": 10, + "description": "Page size", + }, + "key": { + "required": False, + "in": "query", + "type": "str", + "default": "publishedAt", + "enum": ['channelName', 'views', 'reactions', 'forwards', 'publishedAt'], + "description": "Specifies key to sort by", + }, + "sort": { + "required": False, + "in": "query", + "type": "str", + "default": "desc", + "enum": ['asc', 'desc'], + "description": "Specifies sort", + }, + "category": { + "required": False, + "in": "query", + "type": "str", + "default": "", + "enum": ['english', 'korean'], + "description": "Specifies category", + }, + "search_query": { + "required": False, + "in": "query", + "type": "str", + "default": "", + "description": "Specifies search query", + }, + }, + }, + "ticker": { + "path": "/api/v1/ticker", + "method": "GET", + "tag": "ticker", + "summary": "Data", + "requires_auth": True, + "group": "ticker", + "params": { + "exchange": { + "required": True, + "in": "query", + "type": "str", + "description": "Specifes exchange", + }, + "symbol": { + "required": True, + "in": "query", + "type": "str", + "description": "Specifies symbol", + }, + "market": { + "required": False, + "in": "query", + "type": "str", + "enum": ['spot', 'futures'], + "description": "Specifies market", + }, + "currency": { + "required": False, + "in": "query", + "type": "str", + "default": "USD", + "enum": ['KRW', 'USD'], + "description": "Specifies currency applied to price values", + }, + "conversion_base": { + "required": False, + "in": "query", + "type": "str", + "description": "Specifies conversion base applied to price values", + }, + "include_source": { + "required": False, + "in": "query", + "type": "bool", + "default": False, + "description": "When true, include the frame's transport source (ws|rest) in the response.", + }, + }, + }, + "ticker_exchanges": { + "path": "/api/v1/ticker/exchanges", + "method": "GET", + "tag": "ticker", + "summary": "Exchanges", + "requires_auth": False, + "group": "ticker", + "params": { + "market": { + "required": False, + "in": "query", + "type": "str", + "enum": ['spot', 'futures'], + "description": "Specifies market", + }, + }, + }, + "ticker_symbols": { + "path": "/api/v1/ticker/symbols", + "method": "GET", + "tag": "ticker", + "summary": "Symbols", + "requires_auth": False, + "group": "ticker", + "params": { + "exchange": { + "required": True, + "in": "query", + "type": "str", + "description": "Specifes exchange", + }, + "market": { + "required": False, + "in": "query", + "type": "str", + "enum": ['spot', 'futures'], + "description": "Specifies market", + }, + }, + }, + "cex_token_updates": { + "path": "/api/v1/cex/token/updates", + "method": "GET", + "tag": "token", + "summary": "Token Updates", + "requires_auth": True, + "group": "token", + "params": { + "page": { + "required": False, + "in": "query", + "type": "str", + "default": "1", + "description": "Specifies page", + }, + "limit": { + "required": False, + "in": "query", + "type": "str", + "default": "100", + "description": "Specifies limit", + }, + "type": { + "required": False, + "in": "query", + "type": "str", + "enum": ['listed', 'delisted'], + "description": "Specifies type of token update", + }, + }, + }, + "cex_fees": { + "path": "/api/v1/cex/fees", + "method": "GET", + "tag": "trading-fees", + "summary": "Data", + "requires_auth": True, + "group": "trading_fees", + "params": { + "exchange": { + "required": False, + "in": "query", + "type": "str", + "description": "Specifies exchange", + }, + "symbol": { + "required": False, + "in": "query", + "type": "str", + "description": "Specifies symbol", + }, + }, + }, + "cex_fees_exchanges": { + "path": "/api/v1/cex/fees/exchanges", + "method": "GET", + "tag": "trading-fees", + "summary": "Exchanges", + "requires_auth": False, + "group": "trading_fees", + "params": { + }, + }, + "cex_fees_symbols": { + "path": "/api/v1/cex/fees/symbols", + "method": "GET", + "tag": "trading-fees", + "summary": "Symbols", + "requires_auth": False, + "group": "trading_fees", + "params": { + "exchange": { + "required": True, + "in": "query", + "type": "str", + "description": "Specifes exchange", + }, + }, + }, + "wallet_status": { + "path": "/api/v1/wallet-status", + "method": "GET", + "tag": "wallet-status", + "summary": "Data", + "requires_auth": True, + "group": "wallet_status", + "params": { + "exchange": { + "required": False, + "in": "query", + "type": "str", + "description": "Specifes exchange", + }, + "asset": { + "required": True, + "in": "query", + "type": "str", + "description": "Specifies asset", + }, + }, + }, + "wallet_status_assets": { + "path": "/api/v1/wallet-status/assets", + "method": "GET", + "tag": "wallet-status", + "summary": "Assets", + "requires_auth": False, + "group": "wallet_status", + "params": { + "exchange": { + "required": True, + "in": "query", + "type": "str", + "description": "Specifes exchange", + }, + }, + }, + "wallet_status_exchanges": { + "path": "/api/v1/wallet-status/exchanges", + "method": "GET", + "tag": "wallet-status", + "summary": "Exchanges", + "requires_auth": False, + "group": "wallet_status", + "params": { + }, + }, +} + + +# Endpoint index by group for SDK class mapping +GROUPS = {} +for _op_id, _ep in ENDPOINTS.items(): + _group = _ep.get("group", "") + _subgroup = _ep.get("subgroup") + _key = f"{_group}.{_subgroup}" if _subgroup else _group + GROUPS.setdefault(_key, []).append(_op_id) diff --git a/datamaxi/api.py b/datamaxi/api.py index 6232d0b..24df5c3 100644 --- a/datamaxi/api.py +++ b/datamaxi/api.py @@ -7,6 +7,8 @@ from datamaxi.error import ClientError, ServerError from datamaxi.lib.utils import cleanNoneValue from datamaxi.lib.utils import encoded_string +from datamaxi.lib.utils import check_required_parameter +from datamaxi._endpoints import ENDPOINTS class API(object): @@ -65,6 +67,53 @@ def __init__( def query(self, url_path, payload=None): return self.send_request("GET", url_path, payload=payload) + def request_endpoint(self, op_id, **params): + """Dispatch a request described by the generated endpoint registry. + + Looks up ``op_id`` in ``datamaxi._endpoints.ENDPOINTS`` (regenerated + from the backend OpenAPI spec by datamaxi-codegen) and uses it as the + single source of truth for the URL path, HTTP method, path/query split, + required params, and default values. Callers pass wire-level parameter + names as keyword arguments (e.g. ``**{"from": from_unix}``); semantic + validation and response shaping stay in the calling client method. + """ + ep = ENDPOINTS.get(op_id) + if ep is None: + raise ValueError(f"unknown endpoint operation_id: {op_id!r}") + + spec_params = ep.get("params", {}) + + unknown = set(params) - set(spec_params) + if unknown: + raise ValueError( + f"{op_id}: unknown parameter(s) {sorted(unknown)}; " + f"expected one of {sorted(spec_params)}" + ) + + # Resolve each value: caller-supplied, else the registry default. + values = {} + for name, meta in spec_params.items(): + val = params.get(name) + if val is None and "default" in meta: + val = meta["default"] + values[name] = val + + # Enforce params the spec marks required. + for name, meta in spec_params.items(): + if meta.get("required"): + check_required_parameter(values.get(name), name) + + # Split path vs query params; interpolate path params into the URL. + url_path = ep["path"] + query_params = {} + for name, meta in spec_params.items(): + if meta.get("in") == "path": + url_path = url_path.replace("{" + name + "}", str(values[name])) + else: + query_params[name] = values[name] + + return self.send_request(ep["method"], url_path, payload=query_params) + def send_request(self, http_method, url_path, payload=None): if payload is None: payload = {} diff --git a/datamaxi/datamaxi/cex_candle.py b/datamaxi/datamaxi/cex_candle.py index a7fbc93..e1ff898 100644 --- a/datamaxi/datamaxi/cex_candle.py +++ b/datamaxi/datamaxi/cex_candle.py @@ -65,17 +65,15 @@ def __call__( if market not in [SPOT, FUTURES]: raise ValueError("market must be either spot or futures") - params = { - "exchange": exchange, - "market": market, - "symbol": symbol, - "interval": interval, - "from": from_unix, - "to": to_unix, - "currency": currency, - } - - res = self.query("/api/v1/cex/candle", params) + res = self.request_endpoint( + "cex_candle", + exchange=exchange, + market=market, + symbol=symbol, + interval=interval, + currency=currency, + **{"from": from_unix, "to": to_unix}, + ) if res["data"] is None or len(res["data"]) == 0: raise ValueError("no data found") @@ -102,9 +100,7 @@ def exchanges(self, market: str) -> List[str]: if market not in [SPOT, FUTURES]: raise ValueError("market must be either spot or futures") - params = {"market": market} - url_path = "/api/v1/cex/candle/exchanges" - return self.query(url_path, params) + return self.request_endpoint("cex_candle_exchanges", market=market) def symbols(self, exchange: str = None, market: str = None) -> List[Dict]: """Fetch supported symbols for candle data. @@ -123,14 +119,9 @@ def symbols(self, exchange: str = None, market: str = None) -> List[Dict]: if market is not None and market not in [SPOT, FUTURES]: raise ValueError("market must be either spot or futures") - params = {} - if exchange is not None: - params["exchange"] = exchange - if market is not None: - params["market"] = market - - url_path = "/api/v1/cex/candle/symbols" - return self.query(url_path, params) + return self.request_endpoint( + "cex_candle_symbols", exchange=exchange, market=market + ) def intervals(self) -> List[str]: """Fetch supported intervals for candle data. @@ -142,5 +133,4 @@ def intervals(self) -> List[str]: Returns: List of supported intervals """ - url_path = "/api/v1/cex/candle/intervals" - return self.query(url_path) + return self.request_endpoint("cex_candle_intervals") diff --git a/pyproject.toml b/pyproject.toml index 9904679..c5751b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,3 +25,7 @@ dependencies = {file = ["requirements/common.txt"]} [project.urls] Homepage = "https://github.com/bisonai/datamaxi-python" Issues = "https://github.com/bisonai/datamaxi-python/issues" + +[tool.black] +# datamaxi/_endpoints.py is generated by datamaxi-codegen; don't reformat it. +force-exclude = 'datamaxi/_endpoints\.py' diff --git a/setup.cfg b/setup.cfg index caea9d0..16aa1b2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -18,6 +18,7 @@ markers = exclude = .git, build, - dist + dist, + datamaxi/_endpoints.py max-complexity = 10 ignore = E501, W503, W504