From 8af9f12800042dc78bfa3a3f2bcb83ff108da386 Mon Sep 17 00:00:00 2001 From: Jakub Vins Date: Fri, 6 Feb 2026 17:14:43 +0100 Subject: [PATCH 1/2] Fix debank token parsing --- blockapi/v2/api/debank.py | 28 ++++++++++++++++++++-------- blockapi/v2/models.py | 37 +++++++++++++++++++++++++++++++++++-- 2 files changed, 55 insertions(+), 10 deletions(-) diff --git a/blockapi/v2/api/debank.py b/blockapi/v2/api/debank.py index 585a066..523ae85 100644 --- a/blockapi/v2/api/debank.py +++ b/blockapi/v2/api/debank.py @@ -9,7 +9,7 @@ from blockapi.utils.address import make_checksum_address from blockapi.utils.datetime import parse_dt -from blockapi.utils.num import decimals_to_raw, to_decimal +from blockapi.utils.num import decimals_to_raw from blockapi.v2.api.debank_maps import ( COINGECKO_IDS_BY_CONTRACTS, DEBANK_APP_CHAIN_MAP, @@ -42,6 +42,7 @@ DebankModelAppPortfolioItem, DebankModelApp, DebankModelPredictionDetail, + DebankDepositToken, ) logger = logging.getLogger(__name__) @@ -609,7 +610,11 @@ def parse(self, response: list) -> list[DebankApp]: apps = [] for item in response: - app = self._parse_app(item) + try: + app = self._parse_app(item) + except Exception as e: + logger.error(f'Failed to parse app: {e}') + continue if app: apps.append(app) @@ -617,11 +622,7 @@ def parse(self, response: list) -> list[DebankApp]: def _parse_app(self, raw_app: dict) -> Optional[DebankApp]: """Parse a single app from the response.""" - try: - model = DebankModelApp(**raw_app) - except Exception as e: - logger.error(f'Failed to parse app: {e}') - return None + model = DebankModelApp(**raw_app) deposits = [] predictions = [] @@ -679,17 +680,28 @@ def _parse_deposit( self, item: DebankModelAppPortfolioItem, chain: Optional[Blockchain] ) -> Optional[DebankAppDeposit]: """Parse a deposit/common type portfolio item.""" + parsed_tokens = [ + self._parse_token(t.model_dump()) for t in item.asset_token_list or [] + ] + return DebankAppDeposit.from_api( name=item.name, asset_usd_value=item.stats.asset_usd_value, debt_usd_value=item.stats.debt_usd_value, net_usd_value=item.stats.net_usd_value, - tokens=item.asset_token_list, + tokens=[t for t in parsed_tokens if t is not None], chain=chain, position_index=item.position_index, update_at=item.update_at, ) + def _parse_token(self, raw_token: dict) -> Optional[DebankDepositToken]: + try: + return DebankDepositToken.from_api(**raw_token) + except Exception as e: + logger.error(f'Failed to parse deposit token: {e}') + return None + class DebankApi(CustomizableBlockchainApi, BalanceMixin, IPortfolio): """ diff --git a/blockapi/v2/models.py b/blockapi/v2/models.py index 0679caf..d748d7f 100644 --- a/blockapi/v2/models.py +++ b/blockapi/v2/models.py @@ -1197,7 +1197,7 @@ class DebankModelPredictionDetail(BaseModel): event_end_at: Optional[float] = None -class DebankDepositToken(BaseModel): +class DebankModelDepositToken(BaseModel): """Token within deposit/common type portfolio items.""" id: str @@ -1218,7 +1218,7 @@ class DebankModelAppPortfolioItem(BaseModel): detail: dict position_index: str asset_dict: Optional[dict] = None - asset_token_list: list[DebankDepositToken] = Field(default_factory=list) + asset_token_list: list[DebankModelDepositToken] = Field(default_factory=list) update_at: Optional[float] = None proxy_detail: Optional[dict] = None @@ -1281,6 +1281,39 @@ def from_api( ) +@attr.s(auto_attribs=True, slots=True, frozen=True) +class DebankDepositToken: + id: str + symbol: str + name: str + amount: Decimal + app_id: str + price: Decimal + logo_url: Optional[str] + + @classmethod + def from_api( + cls, + *, + id: str, + symbol: str, + name: str, + amount: Union[str, float, int], + app_id: str, + price: Union[str, float, int], + logo_url: Optional[str] = None, + ) -> 'DebankDepositToken': + return cls( + id=id, + symbol=symbol, + name=name, + amount=to_decimal(amount), + app_id=app_id, + price=to_decimal(price), + logo_url=logo_url, + ) + + @attr.s(auto_attribs=True, slots=True, frozen=True) class DebankAppDeposit: """Represents a deposit/holding within a Debank App (e.g., Polymarket cash deposit).""" From f6ec311cad8c0762c409a69a9ff6380f86f41cc8 Mon Sep 17 00:00:00 2001 From: Jakub Vins Date: Tue, 10 Feb 2026 09:49:04 +0100 Subject: [PATCH 2/2] Fix error handling --- blockapi/v2/api/debank.py | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/blockapi/v2/api/debank.py b/blockapi/v2/api/debank.py index 523ae85..083c5aa 100644 --- a/blockapi/v2/api/debank.py +++ b/blockapi/v2/api/debank.py @@ -5,7 +5,7 @@ from typing import Dict, Iterable, List, Optional, Union import attr -from pydantic import BaseModel, validator +from pydantic import BaseModel, ValidationError, validator from blockapi.utils.address import make_checksum_address from blockapi.utils.datetime import parse_dt @@ -610,11 +610,7 @@ def parse(self, response: list) -> list[DebankApp]: apps = [] for item in response: - try: - app = self._parse_app(item) - except Exception as e: - logger.error(f'Failed to parse app: {e}') - continue + app = self._parse_app(item) if app: apps.append(app) @@ -656,11 +652,7 @@ def _parse_prediction( self, item: DebankModelAppPortfolioItem, chain: Optional[Blockchain] ) -> Optional[DebankPrediction]: """Parse a prediction market position.""" - try: - detail = DebankModelPredictionDetail(**item.detail) - except Exception as e: - logger.error(f'Failed to parse prediction detail: {e}') - return None + detail = DebankModelPredictionDetail(**item.detail) return DebankPrediction.from_api( prediction_name=detail.name, @@ -696,11 +688,7 @@ def _parse_deposit( ) def _parse_token(self, raw_token: dict) -> Optional[DebankDepositToken]: - try: - return DebankDepositToken.from_api(**raw_token) - except Exception as e: - logger.error(f'Failed to parse deposit token: {e}') - return None + return DebankDepositToken.from_api(**raw_token) class DebankApi(CustomizableBlockchainApi, BalanceMixin, IPortfolio):