Skip to content
Open
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
16 changes: 14 additions & 2 deletions src/polymarket/_internal/actions/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,15 +275,16 @@ def list_closed_positions_spec(
def list_combo_positions_spec(
*,
user: str,
status: ComboPositionStatus | None = None,
status: ComboPositionStatus | Sequence[ComboPositionStatus] | None = None,
sort: ComboPositionSort | None = None,
condition_id: str | Sequence[str] | None = None,
updated_after: int | None = None,
updated_before: int | None = None,
) -> KeysetPaginatedSpec[ComboPosition]:
if not user:
raise UserInputError("user is required.")
_check_enum("status", status, _COMBO_POSITION_STATUS)
if status is not None:
status = _normalize_combo_status_filter(status)
_check_enum("sort", sort, _COMBO_POSITION_SORT)
if condition_id is not None:
condition_id = _normalize_combo_condition_filter(condition_id)
Expand Down Expand Up @@ -518,6 +519,17 @@ def _check_nonnegative_int(name: str, value: int | None) -> None:
raise UserInputError(f"{name} must be non-negative.")


def _normalize_combo_status_filter(
value: ComboPositionStatus | Sequence[ComboPositionStatus],
) -> ComboPositionStatus | tuple[ComboPositionStatus, ...]:
values = (value,) if isinstance(value, str) else tuple(value)
if not values:
raise UserInputError("status must be a non-empty sequence.")
for item in values:
_check_enum("status", item, _COMBO_POSITION_STATUS)
return value if isinstance(value, str) else values


def _normalize_combo_condition_filter(value: str | Sequence[str]) -> str | tuple[str, ...]:
values = (value,) if isinstance(value, str) else tuple(value)
if not values:
Expand Down
6 changes: 5 additions & 1 deletion src/polymarket/clients/async_public.py
Original file line number Diff line number Diff line change
Expand Up @@ -714,7 +714,7 @@ def list_combo_positions(
self,
*,
user: str,
status: ComboPositionStatus | None = None,
status: ComboPositionStatus | Sequence[ComboPositionStatus] | None = None,
sort: ComboPositionSort | None = None,
condition_id: str | Sequence[str] | None = None,
updated_after: int | None = None,
Expand All @@ -723,6 +723,10 @@ def list_combo_positions(
) -> AsyncPaginator[ComboPosition]:
"""List combo positions for a user.

Args:
status: One status or a non-empty sequence of statuses. Multiple
statuses are matched as alternatives in the given order.

Returns:
An async paginator over matching combo positions.
"""
Expand Down
6 changes: 5 additions & 1 deletion src/polymarket/clients/async_secure.py
Original file line number Diff line number Diff line change
Expand Up @@ -1342,7 +1342,7 @@ def list_combo_positions(
self,
*,
user: str | None = None,
status: ComboPositionStatus | None = None,
status: ComboPositionStatus | Sequence[ComboPositionStatus] | None = None,
sort: ComboPositionSort | None = None,
condition_id: str | Sequence[str] | None = None,
updated_after: int | None = None,
Expand All @@ -1351,6 +1351,10 @@ def list_combo_positions(
) -> AsyncPaginator[ComboPosition]:
"""List combo positions for a user or the authenticated wallet.

Args:
status: One status or a non-empty sequence of statuses. Multiple
statuses are matched as alternatives in the given order.

Returns:
An async paginator over matching combo positions.
"""
Expand Down
6 changes: 5 additions & 1 deletion src/polymarket/clients/public.py
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,7 @@ def list_combo_positions(
self,
*,
user: str,
status: ComboPositionStatus | None = None,
status: ComboPositionStatus | Sequence[ComboPositionStatus] | None = None,
sort: ComboPositionSort | None = None,
condition_id: str | Sequence[str] | None = None,
updated_after: int | None = None,
Expand All @@ -503,6 +503,10 @@ def list_combo_positions(
) -> Paginator[ComboPosition]:
"""List combo positions for a user.

Args:
status: One status or a non-empty sequence of statuses. Multiple
statuses are matched as alternatives in the given order.

Returns:
A paginator over matching combo positions.
"""
Expand Down
6 changes: 5 additions & 1 deletion src/polymarket/clients/secure.py
Original file line number Diff line number Diff line change
Expand Up @@ -871,7 +871,7 @@ def list_combo_positions(
self,
*,
user: str | None = None,
status: ComboPositionStatus | None = None,
status: ComboPositionStatus | Sequence[ComboPositionStatus] | None = None,
sort: ComboPositionSort | None = None,
condition_id: str | Sequence[str] | None = None,
updated_after: int | None = None,
Expand All @@ -880,6 +880,10 @@ def list_combo_positions(
) -> Paginator[ComboPosition]:
"""List combo positions for a user or the authenticated wallet.

Args:
status: One status or a non-empty sequence of statuses. Multiple
statuses are matched as alternatives in the given order.

Returns:
A paginator over matching combo positions.
"""
Expand Down
25 changes: 25 additions & 0 deletions tests/integration/test_data_paginated.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
ClosedPosition,
ComboActivity,
ComboPosition,
ComboPositionStatus,
LeaderboardEntry,
MetaMarketPosition,
Position,
Expand Down Expand Up @@ -84,6 +85,30 @@ def test_list_combo_positions_filters_by_condition_id() -> None:
assert filtered.items[0].condition_id == condition_id


@pytest.mark.integration
def test_list_combo_positions_filters_by_multiple_statuses() -> None:
statuses: list[ComboPositionStatus] = ["RESOLVED_WIN", "RESOLVED_PARTIAL", "RESOLVED_LOSS"]
with PublicClient() as client:
paginator = client.list_combo_positions(user=COMBO_WALLET, status=statuses, page_size=1)
first = paginator.first_page()
pages = [first]
if first.next_cursor is not None:
pages.append(paginator.from_cursor(first.next_cursor).first_page())
assert first.items
assert all(position.status in statuses for page in pages for position in page.items)


@pytest.mark.integration
def test_list_combo_positions_filters_by_single_status() -> None:
status: ComboPositionStatus = "RESOLVED_WIN"
with PublicClient() as client:
page = client.list_combo_positions(
user=COMBO_WALLET, status=status, page_size=10
).first_page()
assert page.items
assert all(p.status == status for p in page.items)


@pytest.mark.integration
def test_list_market_positions_first_page() -> None:
condition_ids = _condition_ids_for_event()
Expand Down
46 changes: 46 additions & 0 deletions tests/unit/test_data_paginated_specs.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,52 @@ def test_list_combo_positions_spec_validates_status() -> None:
data_actions.list_combo_positions_spec(user="0xWALLET", status="CLOSED") # type: ignore[arg-type]


@pytest.mark.parametrize(
"statuses",
[
["RESOLVED_WIN", "RESOLVED_PARTIAL", "RESOLVED_LOSS"],
("RESOLVED_WIN", "RESOLVED_PARTIAL", "RESOLVED_LOSS"),
],
)
def test_list_combo_positions_spec_builds_request_with_multiple_statuses(
statuses: list[str] | tuple[str, ...],
) -> None:
spec = data_actions.list_combo_positions_spec(
user="0xWALLET",
status=statuses, # type: ignore[arg-type]
)
assert spec.base_params == {
"user": "0xWALLET",
"status": "RESOLVED_WIN,RESOLVED_PARTIAL,RESOLVED_LOSS",
}


def test_list_combo_positions_spec_validates_each_status_in_sequence() -> None:
with pytest.raises(UserInputError, match="status"):
data_actions.list_combo_positions_spec(user="0xWALLET", status=["OPEN", "CLOSED"]) # type: ignore[list-item]


def test_list_combo_positions_spec_rejects_empty_status_sequence() -> None:
with pytest.raises(UserInputError, match="status"):
data_actions.list_combo_positions_spec(user="0xWALLET", status=[])


def test_list_combo_positions_spec_rejects_non_string_status_member() -> None:
with pytest.raises(UserInputError, match="status"):
data_actions.list_combo_positions_spec(
user="0xWALLET",
status=["OPEN", 1], # type: ignore[list-item]
)


def test_list_combo_positions_spec_rejects_comma_separated_status_string() -> None:
with pytest.raises(UserInputError, match="status"):
data_actions.list_combo_positions_spec(
user="0xWALLET",
status="RESOLVED_WIN,RESOLVED_LOSS", # type: ignore[arg-type]
)


def test_list_combo_positions_spec_rejects_non_combo_condition_id() -> None:
with pytest.raises(UserInputError, match="combo condition ID"):
data_actions.list_combo_positions_spec(user="0xWALLET", condition_id=_CTF_CONDITION_ID)
Expand Down
Loading