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
6 changes: 6 additions & 0 deletions src/polymarket/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
ClobTrade,
ClosedPosition,
ComboConditionId,
ComboMarket,
ComboMarketOutcome,
ComboMarketOutcomes,
ComboPosition,
ComboPositionLeg,
ComboPositionMarket,
Expand Down Expand Up @@ -188,6 +191,9 @@
"ComboPositionStatus",
"CommentId",
"ComboConditionId",
"ComboMarket",
"ComboMarketOutcome",
"ComboMarketOutcomes",
"ConditionId",
"CtfConditionId",
"ConversionActivity",
Expand Down
87 changes: 87 additions & 0 deletions src/polymarket/_internal/actions/rfq.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
from collections.abc import Callable, Sequence
from typing import Any, TypeVar, cast

from polymarket._internal.request import KeysetPagePayload, KeysetPaginatedSpec, QueryParamValue
from polymarket.errors import UnexpectedResponseError, UserInputError
from polymarket.models import ComboMarket

_T = TypeVar("_T")


def _make_keyset_parser(
items_key: str,
parse_item: Callable[[object], _T],
) -> Callable[[object], KeysetPagePayload[_T]]:
def parse(data: object) -> KeysetPagePayload[_T]:
if not isinstance(data, dict):
raise UnexpectedResponseError("Expected an object response for keyset pagination.")
data_dict = cast(dict[str, Any], data)

if items_key not in data_dict:
raise UnexpectedResponseError(
f"Keyset response is missing required '{items_key}' field."
)
raw = data_dict[items_key]
if not isinstance(raw, list):
raise UnexpectedResponseError(f"Expected '{items_key}' to be an array.")
items_list = cast(list[Any], raw)
items = tuple(parse_item(item) for item in items_list)

if "next_cursor" not in data_dict:
server_cursor: str | None = None
else:
nc = data_dict["next_cursor"]
if nc is None:
server_cursor = None
elif isinstance(nc, str):
if not nc:
raise UnexpectedResponseError(
"'next_cursor' must be a non-empty string when present."
)
server_cursor = nc
else:
raise UnexpectedResponseError(
f"'next_cursor' must be a string when present, got {type(nc).__name__}."
)

return KeysetPagePayload(items=items, server_next_cursor=server_cursor)

return parse


def _add_optional_comma_seq(
params: dict[str, QueryParamValue],
key: str,
value: str | Sequence[str] | None,
) -> None:
if value is None:
return
if isinstance(value, bytes):
raise UserInputError(f"{key} does not accept bytes")
if isinstance(value, str):
if value:
params[key] = value
return
coerced = tuple(value)
if coerced:
params[key] = ",".join(coerced)


def list_combo_markets_spec(
*,
exclude: str | Sequence[str] | None = None,
) -> KeysetPaginatedSpec[ComboMarket]:
params: dict[str, QueryParamValue] = {}
_add_optional_comma_seq(params, "exclude", exclude)

return KeysetPaginatedSpec(
service="rfq",
path="/v1/rfq/combo-markets",
parse_page=_make_keyset_parser("markets", ComboMarket.parse_response),
base_params=params or None,
cursor_param="cursor",
max_page_size=100,
)


__all__ = ["list_combo_markets_spec"]
2 changes: 2 additions & 0 deletions src/polymarket/_internal/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class SyncClientContext:
environment: Environment
gamma: SyncTransport
data: SyncTransport
rfq: SyncTransport
clob: SyncTransport


Expand All @@ -38,6 +39,7 @@ class AsyncClientContext:
environment: Environment
gamma: AsyncTransport
data: AsyncTransport
rfq: AsyncTransport
clob: AsyncTransport


Expand Down
12 changes: 10 additions & 2 deletions src/polymarket/_internal/dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ def _sync_transport_for(ctx: SyncClientContext, service: Service) -> SyncTranspo
return ctx.gamma
case "data":
return ctx.data
case "rfq":
return ctx.rfq
case _ as unreachable:
assert_never(unreachable)

Expand All @@ -42,6 +44,8 @@ def _async_transport_for(ctx: AsyncClientContext, service: Service) -> AsyncTran
return ctx.gamma
case "data":
return ctx.data
case "rfq":
return ctx.rfq
case _ as unreachable:
assert_never(unreachable)

Expand Down Expand Up @@ -157,6 +161,8 @@ def sync_paginate_keyset(
) -> Paginator[T]:
if page_size < 1:
raise UserInputError("page_size must be a positive integer.")
if spec.max_page_size is not None and page_size > spec.max_page_size:
raise UserInputError(f"page_size must be at most {spec.max_page_size}.")
transport = _sync_transport_for(ctx, spec.service)

def fetch(cursor: str | None) -> Page[T]:
Expand All @@ -175,7 +181,7 @@ def fetch(cursor: str | None) -> Page[T]:
"limit": page_size,
}
if server_cursor is not None:
params["after_cursor"] = server_cursor
params[spec.cursor_param] = server_cursor
payload = transport.get_json(spec.path, params=params)
keyset_page = spec.parse_page(payload)
return compute_keyset_page(
Expand All @@ -198,6 +204,8 @@ def async_paginate_keyset(
) -> AsyncPaginator[T]:
if page_size < 1:
raise UserInputError("page_size must be a positive integer.")
if spec.max_page_size is not None and page_size > spec.max_page_size:
raise UserInputError(f"page_size must be at most {spec.max_page_size}.")
transport = _async_transport_for(ctx, spec.service)

async def fetch(cursor: str | None) -> Page[T]:
Expand All @@ -216,7 +224,7 @@ async def fetch(cursor: str | None) -> Page[T]:
"limit": page_size,
}
if server_cursor is not None:
params["after_cursor"] = server_cursor
params[spec.cursor_param] = server_cursor
payload = await transport.get_json(spec.path, params=params)
keyset_page = spec.parse_page(payload)
return compute_keyset_page(
Expand Down
4 changes: 3 additions & 1 deletion src/polymarket/_internal/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from dataclasses import dataclass
from typing import Generic, Literal, TypeVar

Service = Literal["gamma", "data"]
Service = Literal["gamma", "data", "rfq"]
Method = Literal["GET"]

QueryParamScalar = str | int | float | bool
Expand Down Expand Up @@ -41,6 +41,8 @@ class KeysetPaginatedSpec(Generic[T]):
path: str
parse_page: Callable[[object], "KeysetPagePayload[T]"]
base_params: Mapping[str, QueryParamValue] | None = None
cursor_param: str = "after_cursor"
max_page_size: int | None = None


@dataclass(frozen=True, slots=True)
Expand Down
22 changes: 21 additions & 1 deletion src/polymarket/clients/async_public.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from polymarket._internal.actions import data as _data_actions
from polymarket._internal.actions import gamma as _gamma_actions
from polymarket._internal.actions import rewards as _rewards_actions
from polymarket._internal.actions import rfq as _rfq_actions
from polymarket._internal.actions.data import (
ActivitySortBy,
ActivityTypeFilter,
Expand Down Expand Up @@ -47,6 +48,7 @@
from polymarket.environments import PRODUCTION, Environment
from polymarket.errors import RequestRejectedError
from polymarket.models import (
ComboMarket,
Comment,
Event,
LastTradePrice,
Expand Down Expand Up @@ -131,6 +133,7 @@ def __init__(
environment=environment,
gamma=AsyncTransport(base_url=environment.gamma_url, logger=logger),
data=AsyncTransport(base_url=environment.data_url, logger=logger),
rfq=AsyncTransport(base_url=environment.rfq_url, logger=logger),
clob=AsyncTransport(base_url=environment.clob_url, logger=logger),
)
self._market_manager: ClobMarketStreamManager | None = None
Expand Down Expand Up @@ -289,7 +292,10 @@ async def close(self) -> None:
try:
await self._ctx.data.close()
finally:
await self._ctx.clob.close()
try:
await self._ctx.rfq.close()
finally:
await self._ctx.clob.close()

async def get_market(
self,
Expand Down Expand Up @@ -895,6 +901,20 @@ def list_markets(
)
return async_paginate_keyset(self._ctx, spec, page_size=page_size)

def list_combo_markets(
self,
*,
exclude: str | Sequence[str] | None = None,
page_size: int = 20,
) -> AsyncPaginator[ComboMarket]:
"""List markets available for Combos.

Returns:
An async paginator over matching Combo markets.
"""
spec = _rfq_actions.list_combo_markets_spec(exclude=exclude)
return async_paginate_keyset(self._ctx, spec, page_size=page_size)

def list_series(
self,
*,
Expand Down
19 changes: 19 additions & 0 deletions src/polymarket/clients/async_secure.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from polymarket._internal.actions import data as _data_actions
from polymarket._internal.actions import gamma as _gamma_actions
from polymarket._internal.actions import rewards as _rewards_actions
from polymarket._internal.actions import rfq as _rfq_actions
from polymarket._internal.actions.data import (
ActivitySortBy,
ActivityTypeFilter,
Expand Down Expand Up @@ -144,6 +145,7 @@
BuilderFeeRates,
BuilderTrade,
ClobTrade,
ComboMarket,
Comment,
Event,
LastTradePrice,
Expand Down Expand Up @@ -412,6 +414,7 @@ def _construct_for_wallet(

gamma = AsyncTransport(base_url=environment.gamma_url, logger=logger)
data = AsyncTransport(base_url=environment.data_url, logger=logger)
rfq = AsyncTransport(base_url=environment.rfq_url, logger=logger)
clob = AsyncTransport(base_url=environment.clob_url, logger=logger)
relayer_resolver = make_relayer_header_resolver(api_key) if api_key is not None else None
relayer = AsyncTransport(
Expand All @@ -431,6 +434,7 @@ def _construct_for_wallet(
environment=environment,
gamma=gamma,
data=data,
rfq=rfq,
clob=clob,
signer=signer,
credentials=credentials,
Expand Down Expand Up @@ -684,6 +688,7 @@ async def close(self) -> None:
_RfqSessionCloser(self._close_rfq_session),
ctx.gamma,
ctx.data,
ctx.rfq,
ctx.clob,
ctx.secure_clob,
ctx.relayer,
Expand Down Expand Up @@ -1324,6 +1329,20 @@ def list_markets(
)
return async_paginate_keyset(self._ctx, spec, page_size=page_size)

def list_combo_markets(
self,
*,
exclude: str | Sequence[str] | None = None,
page_size: int = 20,
) -> AsyncPaginator[ComboMarket]:
"""List markets available for Combos.

Returns:
An async paginator over matching Combo markets.
"""
spec = _rfq_actions.list_combo_markets_spec(exclude=exclude)
return async_paginate_keyset(self._ctx, spec, page_size=page_size)

def list_series(
self,
*,
Expand Down
22 changes: 21 additions & 1 deletion src/polymarket/clients/public.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from polymarket._internal.actions import data as _data_actions
from polymarket._internal.actions import gamma as _gamma_actions
from polymarket._internal.actions import rewards as _rewards_actions
from polymarket._internal.actions import rfq as _rfq_actions
from polymarket._internal.actions.data import (
ActivitySortBy,
ActivityTypeFilter,
Expand Down Expand Up @@ -45,6 +46,7 @@
from polymarket.environments import PRODUCTION, Environment
from polymarket.errors import RequestRejectedError
from polymarket.models import (
ComboMarket,
Comment,
Event,
LastTradePrice,
Expand Down Expand Up @@ -107,6 +109,7 @@ def __init__(
environment=environment,
gamma=SyncTransport(base_url=environment.gamma_url, logger=logger),
data=SyncTransport(base_url=environment.data_url, logger=logger),
rfq=SyncTransport(base_url=environment.rfq_url, logger=logger),
clob=SyncTransport(base_url=environment.clob_url, logger=logger),
)

Expand Down Expand Up @@ -134,7 +137,10 @@ def close(self) -> None:
try:
self._ctx.data.close()
finally:
self._ctx.clob.close()
try:
self._ctx.rfq.close()
finally:
self._ctx.clob.close()

def get_market(
self,
Expand Down Expand Up @@ -749,6 +755,20 @@ def list_markets(
)
return sync_paginate_keyset(self._ctx, spec, page_size=page_size)

def list_combo_markets(
self,
*,
exclude: str | Sequence[str] | None = None,
page_size: int = 20,
) -> Paginator[ComboMarket]:
"""List markets available for Combos.

Returns:
A paginator over matching Combo markets.
"""
spec = _rfq_actions.list_combo_markets_spec(exclude=exclude)
return sync_paginate_keyset(self._ctx, spec, page_size=page_size)

def list_series(
self,
*,
Expand Down
Loading
Loading