From 99a320329109ef5ccf6b220f9191476c0266ccd3 Mon Sep 17 00:00:00 2001 From: icc Date: Sun, 24 May 2026 09:16:38 +0800 Subject: [PATCH 1/2] fix: export Python websocket return types --- sdks/python/pmxt/__init__.py | 4 ++++ sdks/python/tests/test_public_exports.py | 30 ++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 sdks/python/tests/test_public_exports.py diff --git a/sdks/python/pmxt/__init__.py b/sdks/python/pmxt/__init__.py index 09ba6770..0c91c98e 100644 --- a/sdks/python/pmxt/__init__.py +++ b/sdks/python/pmxt/__init__.py @@ -46,6 +46,7 @@ OrderLevel, Trade, UserTrade, + FirehoseEvent, PaginatedMarketsResult, PaginatedEventsResult, Order, @@ -60,6 +61,7 @@ EventMatchResult, PriceComparison, ArbitrageOpportunity, + SubscribedAddressSnapshot, MatchRelation, ) @@ -179,6 +181,7 @@ def restart_server(): "OrderLevel", "Trade", "UserTrade", + "FirehoseEvent", "PaginatedMarketsResult", "PaginatedEventsResult", "Order", @@ -189,5 +192,6 @@ def restart_server(): "EventMatchResult", "PriceComparison", "ArbitrageOpportunity", + "SubscribedAddressSnapshot", "MatchRelation", ] diff --git a/sdks/python/tests/test_public_exports.py b/sdks/python/tests/test_public_exports.py new file mode 100644 index 00000000..fd441598 --- /dev/null +++ b/sdks/python/tests/test_public_exports.py @@ -0,0 +1,30 @@ +import ast +from pathlib import Path + + +def test_websocket_return_types_are_public_exports(): + init_path = Path(__file__).resolve().parents[1] / "pmxt" / "__init__.py" + tree = ast.parse(init_path.read_text(encoding="utf-8")) + + imported_models = set() + public_exports = set() + + for node in tree.body: + if isinstance(node, ast.ImportFrom) and node.module == "models": + imported_models.update(alias.name for alias in node.names) + elif ( + isinstance(node, ast.Assign) + and len(node.targets) == 1 + and isinstance(node.targets[0], ast.Name) + and node.targets[0].id == "__all__" + and isinstance(node.value, ast.List) + ): + public_exports.update( + item.value + for item in node.value.elts + if isinstance(item, ast.Constant) and isinstance(item.value, str) + ) + + expected = {"FirehoseEvent", "SubscribedAddressSnapshot"} + assert expected <= imported_models + assert expected <= public_exports From 01094d5ab59ec93290e382fc693031daa83605fb Mon Sep 17 00:00:00 2001 From: icc Date: Sun, 24 May 2026 22:11:57 +0800 Subject: [PATCH 2/2] fix: keep legacy Polymarket_us Python alias --- core/scripts/generate-python-exchanges.js | 27 ++++++++++++++-- sdks/python/pmxt/__init__.py | 3 +- sdks/python/pmxt/_exchanges.py | 3 ++ sdks/python/tests/test_public_exports.py | 39 +++++++++++++++++++++++ 4 files changed, 69 insertions(+), 3 deletions(-) diff --git a/core/scripts/generate-python-exchanges.js b/core/scripts/generate-python-exchanges.js index 5bed28fb..da21a1d4 100644 --- a/core/scripts/generate-python-exchanges.js +++ b/core/scripts/generate-python-exchanges.js @@ -35,6 +35,13 @@ function toClassName(name) { .join(''); } +function toLegacyClassName(name) { + return name + .split('-') + .map(part => part.charAt(0).toUpperCase() + part.slice(1)) + .join(''); +} + function parseExchanges(content) { const startIdx = content.indexOf('function createExchange('); if (startIdx === -1) throw new Error('createExchange not found in exchange-factory.ts'); @@ -205,6 +212,9 @@ function generateClass(exchange) { const appTs = fs.readFileSync(APP_TS_PATH, 'utf8'); const exchanges = parseExchanges(appTs); +const legacyAliases = exchanges + .map(ex => ({ legacyName: toLegacyClassName(ex.name), className: toClassName(ex.name) })) + .filter(alias => alias.legacyName !== alias.className); const header = [ '# This file is auto-generated by core/scripts/generate-python-exchanges.js', @@ -220,8 +230,17 @@ const header = [ ].join('\n'); const body = exchanges.map(generateClass).join('\n\n\n'); +const aliasBlock = legacyAliases.length + ? [ + '', + '', + '# Backwards-compatible aliases for exchange classes generated before underscore handling.', + ...legacyAliases.map(alias => `${alias.legacyName} = ${alias.className}`), + '', + ].join('\n') + : '\n'; -fs.writeFileSync(OUTPUT_PATH, header + body + '\n'); +fs.writeFileSync(OUTPUT_PATH, header + body + aliasBlock); console.log(`Generated ${exchanges.length} exchange classes -> ${path.relative(process.cwd(), OUTPUT_PATH)}`); for (const ex of exchanges) { console.log(` ${toClassName(ex.name)} (exchange_name="${ex.name}")`); @@ -230,7 +249,11 @@ for (const ex of exchanges) { // --------------------------------------------------------------------------- // Update __init__.py imports and __all__ to match generated exchanges // --------------------------------------------------------------------------- -const classNames = exchanges.map(ex => toClassName(ex.name)); +const classNames = exchanges.flatMap(ex => { + const className = toClassName(ex.name); + const legacyName = toLegacyClassName(ex.name); + return legacyName === className ? [className] : [className, legacyName]; +}); const importList = classNames.join(', '); let init = fs.readFileSync(INIT_PATH, 'utf8'); diff --git a/sdks/python/pmxt/__init__.py b/sdks/python/pmxt/__init__.py index 0c91c98e..5d1c9794 100644 --- a/sdks/python/pmxt/__init__.py +++ b/sdks/python/pmxt/__init__.py @@ -17,7 +17,7 @@ """ from .client import Exchange -from ._exchanges import Polymarket, Limitless, Kalshi, KalshiDemo, Probable, Baozi, Myriad, Opinion, Metaculus, Smarkets, PolymarketUS, Hyperliquid, GeminiTitan, Mock, Router +from ._exchanges import Polymarket, Limitless, Kalshi, KalshiDemo, Probable, Baozi, Myriad, Opinion, Metaculus, Smarkets, PolymarketUS, Polymarket_us, Hyperliquid, GeminiTitan, Mock, Router from .router import Router from .server_manager import ServerManager from .errors import ( @@ -146,6 +146,7 @@ def restart_server(): "Metaculus", "Smarkets", "PolymarketUS", + "Polymarket_us", "Hyperliquid", "GeminiTitan", "Mock", diff --git a/sdks/python/pmxt/_exchanges.py b/sdks/python/pmxt/_exchanges.py index 0f6fb9c5..1a61bf0c 100644 --- a/sdks/python/pmxt/_exchanges.py +++ b/sdks/python/pmxt/_exchanges.py @@ -513,3 +513,6 @@ def __init__( auto_start_server=auto_start_server, pmxt_api_key=pmxt_api_key, ) + +# Backwards-compatible aliases for exchange classes generated before underscore handling. +Polymarket_us = PolymarketUS diff --git a/sdks/python/tests/test_public_exports.py b/sdks/python/tests/test_public_exports.py index fd441598..852a9080 100644 --- a/sdks/python/tests/test_public_exports.py +++ b/sdks/python/tests/test_public_exports.py @@ -28,3 +28,42 @@ def test_websocket_return_types_are_public_exports(): expected = {"FirehoseEvent", "SubscribedAddressSnapshot"} assert expected <= imported_models assert expected <= public_exports + + +def test_legacy_polymarket_us_alias_stays_public(): + init_path = Path(__file__).resolve().parents[1] / "pmxt" / "__init__.py" + exchanges_path = Path(__file__).resolve().parents[1] / "pmxt" / "_exchanges.py" + + init_tree = ast.parse(init_path.read_text(encoding="utf-8")) + exchange_imports = set() + public_exports = set() + + for node in init_tree.body: + if isinstance(node, ast.ImportFrom) and node.module == "_exchanges": + exchange_imports.update(alias.name for alias in node.names) + elif ( + isinstance(node, ast.Assign) + and len(node.targets) == 1 + and isinstance(node.targets[0], ast.Name) + and node.targets[0].id == "__all__" + and isinstance(node.value, ast.List) + ): + public_exports.update( + item.value + for item in node.value.elts + if isinstance(item, ast.Constant) and isinstance(item.value, str) + ) + + exchanges_tree = ast.parse(exchanges_path.read_text(encoding="utf-8")) + aliases = { + node.targets[0].id: node.value.id + for node in exchanges_tree.body + if isinstance(node, ast.Assign) + and len(node.targets) == 1 + and isinstance(node.targets[0], ast.Name) + and isinstance(node.value, ast.Name) + } + + assert "Polymarket_us" in exchange_imports + assert "Polymarket_us" in public_exports + assert aliases["Polymarket_us"] == "PolymarketUS"