Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
19 changes: 17 additions & 2 deletions src/polymarket/_internal/actions/gamma.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ def _add_optional(
params[key] = value


ClosedFilter: TypeAlias = bool | Literal["any"]
DateFilter: TypeAlias = str | date
TimestampFilter: TypeAlias = str | datetime

Expand Down Expand Up @@ -198,6 +199,11 @@ def _add_optional_seq(
params[key] = coerced


def _check_closed(value: ClosedFilter | None) -> None:
if value is not None and not isinstance(value, bool) and value != "any":
raise UserInputError('closed must be True, False, None, or "any"')


def _check_recurrence(value: Recurrence | None) -> None:
if value is not None and value not in {"daily", "weekly", "monthly"}:
raise UserInputError("recurrence must be one of: daily, weekly, monthly")
Expand Down Expand Up @@ -408,6 +414,7 @@ def get_comment_thread_spec(


__all__ = [
"ClosedFilter",
"CommentParentEntityType",
"DateFilter",
"Recurrence",
Expand Down Expand Up @@ -441,7 +448,7 @@ def get_comment_thread_spec(
def list_events_spec(
*,
ascending: bool | None = None,
closed: bool = False,
closed: ClosedFilter = False,
cyom: bool | None = None,
end_date_max: TimestampFilter | None = None,
end_date_min: TimestampFilter | None = None,
Expand Down Expand Up @@ -481,6 +488,7 @@ def list_events_spec(
) -> KeysetPaginatedSpec[Event]:
_check_recurrence(recurrence)
_check_tag_match(tag_match)
_check_closed(closed)

params: dict[str, QueryParamValue] = {}
_add_optional(params, "ascending", ascending)
Expand Down Expand Up @@ -533,7 +541,7 @@ def list_events_spec(
def list_markets_spec(
*,
ascending: bool | None = None,
closed: bool | None = None,
closed: ClosedFilter | None = None,
clob_token_ids: str | Sequence[str] | None = None,
condition_ids: str | Sequence[str] | None = None,
cyom: bool | None = None,
Expand Down Expand Up @@ -564,6 +572,7 @@ def list_markets_spec(
volume_num_min: float | None = None,
) -> KeysetPaginatedSpec[Market]:
_check_tag_match(tag_match)
_check_closed(closed)

params: dict[str, QueryParamValue] = {}
_add_optional(params, "ascending", ascending)
Expand Down Expand Up @@ -597,6 +606,12 @@ def list_markets_spec(
_add_optional(params, "volume_num_max", volume_num_max)
_add_optional(params, "volume_num_min", volume_num_min)

if closed == "any" and not any(
key in params
for key in ("id", "slug", "clob_token_ids", "condition_ids", "position_ids", "question_ids")
):
raise UserInputError('closed="any" requires at least one market identifier filter')

return KeysetPaginatedSpec(
service="gamma",
path="/markets/keyset",
Expand Down
13 changes: 10 additions & 3 deletions src/polymarket/clients/async_public.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from polymarket._internal.actions import rewards as _rewards_actions
from polymarket._internal.actions import rfq as _rfq_actions
from polymarket._internal.actions.gamma import (
ClosedFilter,
CommentParentEntityType,
DateFilter,
Recurrence,
Expand Down Expand Up @@ -999,7 +1000,7 @@ def list_events(
self,
*,
ascending: bool | None = None,
closed: bool = False,
closed: ClosedFilter = False,
cyom: bool | None = None,
end_date_max: TimestampFilter | None = None,
end_date_min: TimestampFilter | None = None,
Expand Down Expand Up @@ -1040,7 +1041,8 @@ def list_events(
) -> AsyncPaginator[Event]:
"""List events.

Defaults to open events. Pass ``closed=True`` to list settled events.
Defaults to open events. Pass ``closed=True`` to list settled events
or ``closed="any"`` to include both open and closed events.

Returns:
An async paginator over matching events.
Expand Down Expand Up @@ -1098,7 +1100,7 @@ def list_markets(
self,
*,
ascending: bool | None = None,
closed: bool | None = None,
closed: ClosedFilter | None = None,
clob_token_ids: str | Sequence[str] | None = None,
condition_ids: str | Sequence[str] | None = None,
cyom: bool | None = None,
Expand Down Expand Up @@ -1131,6 +1133,11 @@ def list_markets(
) -> AsyncPaginator[Market]:
"""List markets.

Omitted ``closed`` (or ``None``) lists open markets, as does ``False``.
Pass ``True`` for closed markets, or ``"any"`` for both. ``"any"`` requires
at least one of ``ids``, ``slug``, ``clob_token_ids``, ``condition_ids``,
``position_ids``, or ``question_ids``.

Markets that cannot be represented by the binary Market model are
omitted from results.

Expand Down
12 changes: 9 additions & 3 deletions src/polymarket/clients/async_secure.py
Original file line number Diff line number Diff line change
Expand Up @@ -1873,7 +1873,7 @@ def list_events(
self,
*,
ascending: bool | None = None,
closed: bool = False,
closed: bool | Literal["any"] = False,
cyom: bool | None = None,
end_date_max: TimestampFilter | None = None,
end_date_min: TimestampFilter | None = None,
Expand Down Expand Up @@ -1914,7 +1914,8 @@ def list_events(
) -> AsyncPaginator[Event]:
"""List events.

Defaults to open events. Pass ``closed=True`` to list settled events.
Defaults to open events. Pass ``closed=True`` to list settled events
or ``closed="any"`` to include both open and closed events.

Returns:
An async paginator over matching events.
Expand Down Expand Up @@ -1965,7 +1966,7 @@ def list_markets(
self,
*,
ascending: bool | None = None,
closed: bool | None = None,
closed: bool | Literal["any"] | None = None,
clob_token_ids: str | Sequence[str] | None = None,
condition_ids: str | Sequence[str] | None = None,
cyom: bool | None = None,
Expand Down Expand Up @@ -1998,6 +1999,11 @@ def list_markets(
) -> AsyncPaginator[Market]:
"""List markets.

Omitted ``closed`` (or ``None``) lists open markets, as does ``False``.
Pass ``True`` for closed markets, or ``"any"`` for both. ``"any"`` requires
at least one of ``ids``, ``slug``, ``clob_token_ids``, ``condition_ids``,
``position_ids``, or ``question_ids``.

Markets that cannot be represented by the binary Market model are
omitted from results.

Expand Down
13 changes: 10 additions & 3 deletions src/polymarket/clients/public.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from polymarket._internal.actions import rewards as _rewards_actions
from polymarket._internal.actions import rfq as _rfq_actions
from polymarket._internal.actions.gamma import (
ClosedFilter,
CommentParentEntityType,
DateFilter,
Recurrence,
Expand Down Expand Up @@ -759,7 +760,7 @@ def list_events(
self,
*,
ascending: bool | None = None,
closed: bool = False,
closed: ClosedFilter = False,
cyom: bool | None = None,
end_date_max: TimestampFilter | None = None,
end_date_min: TimestampFilter | None = None,
Expand Down Expand Up @@ -800,7 +801,8 @@ def list_events(
) -> Paginator[Event]:
"""List events.

Defaults to open events. Pass ``closed=True`` to list settled events.
Defaults to open events. Pass ``closed=True`` to list settled events
or ``closed="any"`` to include both open and closed events.

Returns:
A paginator over matching events.
Expand Down Expand Up @@ -863,7 +865,7 @@ def list_markets(
self,
*,
ascending: bool | None = None,
closed: bool | None = None,
closed: ClosedFilter | None = None,
clob_token_ids: str | Sequence[str] | None = None,
condition_ids: str | Sequence[str] | None = None,
cyom: bool | None = None,
Expand Down Expand Up @@ -896,6 +898,11 @@ def list_markets(
) -> Paginator[Market]:
"""List markets.

Omitted ``closed`` (or ``None``) lists open markets, as does ``False``.
Pass ``True`` for closed markets, or ``"any"`` for both. ``"any"`` requires
at least one of ``ids``, ``slug``, ``clob_token_ids``, ``condition_ids``,
``position_ids``, or ``question_ids``.

Markets that cannot be represented by the binary Market model are
omitted from results.

Expand Down
12 changes: 9 additions & 3 deletions src/polymarket/clients/secure.py
Original file line number Diff line number Diff line change
Expand Up @@ -1249,7 +1249,7 @@ def list_events(
self,
*,
ascending: bool | None = None,
closed: bool = False,
closed: bool | Literal["any"] = False,
cyom: bool | None = None,
end_date_max: TimestampFilter | None = None,
end_date_min: TimestampFilter | None = None,
Expand Down Expand Up @@ -1290,7 +1290,8 @@ def list_events(
) -> Paginator[Event]:
"""List events.

Defaults to open events. Pass ``closed=True`` to list settled events.
Defaults to open events. Pass ``closed=True`` to list settled events
or ``closed="any"`` to include both open and closed events.

Returns:
A paginator over matching events.
Expand Down Expand Up @@ -1341,7 +1342,7 @@ def list_markets(
self,
*,
ascending: bool | None = None,
closed: bool | None = None,
closed: bool | Literal["any"] | None = None,
clob_token_ids: str | Sequence[str] | None = None,
condition_ids: str | Sequence[str] | None = None,
cyom: bool | None = None,
Expand Down Expand Up @@ -1374,6 +1375,11 @@ def list_markets(
) -> Paginator[Market]:
"""List markets.

Omitted ``closed`` (or ``None``) lists open markets, as does ``False``.
Pass ``True`` for closed markets, or ``"any"`` for both. ``"any"`` requires
at least one of ``ids``, ``slug``, ``clob_token_ids``, ``condition_ids``,
``position_ids``, or ``question_ids``.

Markets that cannot be represented by the binary Market model are
omitted from results.

Expand Down
69 changes: 69 additions & 0 deletions tests/unit/test_gamma_closed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# pyright: reportPrivateUsage=false
import asyncio
import dataclasses
from typing import assert_type

import httpx

from polymarket import AsyncPublicClient, AsyncSecureClient, PublicClient, SecureClient
from polymarket.clients._transport import AsyncTransport, SyncTransport
from polymarket.models import Event, Market
from polymarket.pagination import AsyncPaginator, Paginator


def check_sync_types(client: PublicClient | SecureClient) -> None:
assert_type(client.list_markets(closed="any", ids=42), Paginator[Market])
assert_type(client.list_events(closed="any"), Paginator[Event])


def check_async_types(client: AsyncPublicClient | AsyncSecureClient) -> None:
assert_type(client.list_markets(closed="any", slug="market"), AsyncPaginator[Market])
assert_type(client.list_events(closed="any"), AsyncPaginator[Event])


def test_closed_any_reaches_sync_and_async_keyset_requests() -> None:
captured: list[httpx.Request] = []

def handler(request: httpx.Request) -> httpx.Response:
captured.append(request)
kind = "markets" if request.url.path == "/markets/keyset" else "events"
return httpx.Response(200, json={kind: [], "next_cursor": None})

with PublicClient() as client:
client._ctx.gamma.close()
client._ctx = dataclasses.replace(
client._ctx,
gamma=SyncTransport(
base_url="https://example.test",
client=httpx.Client(
base_url="https://example.test", transport=httpx.MockTransport(handler)
),
),
)
assert client.list_markets(closed="any", ids=42).first_page().items == ()
assert client.list_events(closed="any").first_page().items == ()

async def run() -> None:
async with AsyncPublicClient() as client:
await client._ctx.gamma.close()
client._ctx = dataclasses.replace(
client._ctx,
gamma=AsyncTransport(
base_url="https://example.test",
client=httpx.AsyncClient(
base_url="https://example.test", transport=httpx.MockTransport(handler)
),
),
)
assert (await client.list_markets(closed="any", ids=42).first_page()).items == ()
assert (await client.list_events(closed="any").first_page()).items == ()

asyncio.run(run())
assert [request.url.path for request in captured] == [
"/markets/keyset",
"/events/keyset",
"/markets/keyset",
"/events/keyset",
]
assert all(request.url.params["closed"] == "any" for request in captured)
assert captured[0].url.params["id"] == captured[2].url.params["id"] == "42"
48 changes: 48 additions & 0 deletions tests/unit/test_gamma_paginated_specs.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import Any, Literal

import pytest

from polymarket._internal.actions import gamma as gamma_actions
Expand Down Expand Up @@ -537,3 +539,49 @@ def test_comments_paginate_by_cursor_selects_supported_reads_only(
)
is expected
)


@pytest.mark.parametrize("closed", [False, True, "any"])
def test_list_events_spec_preserves_closed_filter(closed: bool | Literal["any"]) -> None:
assert gamma_actions.list_events_spec(closed=closed).base_params == {"closed": closed}


@pytest.mark.parametrize("closed", [None, False, True, "any"])
def test_list_markets_spec_preserves_closed_filter(
closed: bool | Literal["any"] | None,
) -> None:
spec = gamma_actions.list_markets_spec(closed=closed, ids=42)
expected: dict[str, object] = {"id": (42,)}
if closed is not None:
expected["closed"] = closed
assert spec.base_params == expected


@pytest.mark.parametrize(
"kwargs",
[
{"ids": [42]},
{"slug": "market"},
{"clob_token_ids": "token"},
{"condition_ids": "condition"},
{"position_ids": "position"},
{"question_ids": "question"},
],
)
def test_list_markets_any_accepts_each_identifier(kwargs: dict[str, Any]) -> None:
spec = gamma_actions.list_markets_spec(closed="any", **kwargs)
assert spec.base_params is not None
assert spec.base_params["closed"] == "any"


@pytest.mark.parametrize("kwargs", [{}, {"ids": []}, {"slug": []}, {"tag_id": 1}])
def test_list_markets_any_requires_identifier(kwargs: dict[str, Any]) -> None:
with pytest.raises(UserInputError, match="requires at least one market identifier"):
gamma_actions.list_markets_spec(closed="any", **kwargs)


@pytest.mark.parametrize("closed", ["invalid", 1, "false"])
def test_list_specs_reject_invalid_closed(closed: Any) -> None:
for build_spec in (gamma_actions.list_events_spec, gamma_actions.list_markets_spec):
with pytest.raises(UserInputError, match="closed must be"):
build_spec(closed=closed)
Loading