Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion src/polymarket/models/data/activity.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@

from polymarket.errors import UnexpectedResponseError
from polymarket.models.base import BaseModel
from polymarket.models.gamma.common import parse_epoch_seconds_optional, parse_optional_decimal
from polymarket.models.gamma.common import (
empty_string_to_none,
parse_epoch_seconds_optional,
parse_optional_decimal,
)
from polymarket.models.types import (
CtfConditionId,
TokenId,
Expand Down Expand Up @@ -58,6 +62,11 @@ def _parse_decimal(cls, value: object) -> Decimal | None:
def _parse_timestamp(cls, value: object) -> datetime | None:
return parse_epoch_seconds_optional(value)

@field_validator("icon", mode="before")
@classmethod
def _normalize_icon(cls, value: object) -> object | None:
return empty_string_to_none(value)


ActivityType = Literal[
"TRADE",
Expand Down
11 changes: 11 additions & 0 deletions src/polymarket/models/data/portfolio.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from polymarket.models.base import BaseModel
from polymarket.models.gamma.common import (
empty_string_to_none,
parse_epoch_seconds_optional,
parse_optional_date,
parse_optional_decimal,
Expand Down Expand Up @@ -100,6 +101,11 @@ def _parse_decimal(cls, value: object) -> Decimal | None:
def _parse_end_date(cls, value: object) -> date | None:
return parse_optional_date(value)

@field_validator("icon", mode="before")
@classmethod
def _normalize_icon(cls, value: object) -> object | None:
return empty_string_to_none(value)

def _repr_html_(self) -> str:
from polymarket._jupyter import card, safe_html_repr, truncate_mid

Expand Down Expand Up @@ -170,6 +176,11 @@ def _parse_timestamp(cls, value: object) -> datetime | None:
def _parse_end_date(cls, value: object) -> date | None:
return parse_optional_date(value)

@field_validator("icon", mode="before")
@classmethod
def _normalize_icon(cls, value: object) -> object | None:
return empty_string_to_none(value)


class ComboPositionMarketEvent(BaseModel):
event_id: str | None = None
Expand Down
12 changes: 11 additions & 1 deletion tests/unit/test_data_activity.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
YieldActivity,
)
from polymarket.errors import UnexpectedResponseError
from polymarket.models.data.activity import parse_activities, parse_activity
from polymarket.models.data.activity import Trade, parse_activities, parse_activity

_CONDITION_ID = "0x5c19f205507ce03ff5f3be08a8090a5969ea6870cc07b902a4ca2e61dfe48fdd"

Expand Down Expand Up @@ -137,6 +137,16 @@ def test_market_event_empty_icon_normalizes_to_none() -> None:
assert activity.icon is None


def test_trade_model_empty_icon_normalizes_to_none() -> None:
trade = Trade.parse_response({"icon": ""})
assert trade.icon is None


def test_trade_model_keeps_populated_icon() -> None:
trade = Trade.parse_response({"icon": "https://example.test/icon.png"})
assert trade.icon == "https://example.test/icon.png"


def test_market_event_variants_parse() -> None:
for activity_type, expected_class in [
("SPLIT", SplitActivity),
Expand Down
22 changes: 22 additions & 0 deletions tests/unit/test_data_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@
from polymarket.errors import UnexpectedResponseError
from polymarket.models.data import (
BuilderVolumeEntry,
ClosedPosition,
ComboPosition,
Holder,
LiveVolume,
MetaHolder,
OpenInterest,
PortfolioValue,
Position,
TradedMarketCount,
)

Expand Down Expand Up @@ -159,6 +161,26 @@ def test_traded_market_count_parses_payload() -> None:
assert count.traded == 42


def test_position_empty_icon_normalizes_to_none() -> None:
position = Position.parse_response({"conditionId": _CTF_CONDITION_ID, "icon": ""})

assert position.icon is None


def test_position_keeps_populated_icon() -> None:
position = Position.parse_response(
{"conditionId": _CTF_CONDITION_ID, "icon": "https://example.test/icon.png"}
)

assert position.icon == "https://example.test/icon.png"


def test_closed_position_empty_icon_normalizes_to_none() -> None:
position = ClosedPosition.parse_response({"conditionId": _CTF_CONDITION_ID, "icon": ""})

assert position.icon is None


def test_builder_volume_entry_renames_dt_to_bucket_at() -> None:
entry = BuilderVolumeEntry.parse_response(
{
Expand Down
Loading