pmxt 2.46.2__tar.gz → 2.46.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pmxt-2.46.2 → pmxt-2.46.4}/PKG-INFO +1 -1
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/__init__.py +1 -1
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/api_client.py +1 -1
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/configuration.py +1 -1
- {pmxt-2.46.2 → pmxt-2.46.4}/pmxt/__init__.py +1 -1
- {pmxt-2.46.2 → pmxt-2.46.4}/pmxt/_server/server/bundled.js +56 -11
- {pmxt-2.46.2 → pmxt-2.46.4}/pmxt/client.py +1 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/pmxt/ws_client.py +124 -49
- {pmxt-2.46.2 → pmxt-2.46.4}/pmxt.egg-info/PKG-INFO +1 -1
- {pmxt-2.46.2 → pmxt-2.46.4}/pmxt.egg-info/SOURCES.txt +2 -1
- {pmxt-2.46.2 → pmxt-2.46.4}/pyproject.toml +1 -1
- {pmxt-2.46.2 → pmxt-2.46.4}/tests/test_streaming_ws_transport.py +2 -0
- pmxt-2.46.4/tests/test_ws_client.py +135 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/README.md +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/api/__init__.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/api/data_feeds_api.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/api/default_api.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/api_response.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/exceptions.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/__init__.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/arbitrage_opportunity.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/balance.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/base_request.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/base_response.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/build_order200_response.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/build_order_request.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/built_order.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/built_order_tx.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/cancel_order_request.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/close_request.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/compare_market_prices200_response.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/compare_market_prices_request.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/create_order200_response.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/create_order_params.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/create_order_request.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/error_detail.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/error_response.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/event_fetch_params.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/event_filter_criteria.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/event_filter_criteria_total_volume.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/event_match_result.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/exchange_credentials.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/exchange_credentials_signature_type.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/execution_price_result.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/feed_fetch_historical_prices200_response.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/feed_fetch_ohlcv200_response.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/feed_fetch_oracle_history200_response.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/feed_fetch_oracle_round200_response.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/feed_fetch_order_book200_response.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/feed_fetch_order_book200_response_data.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/feed_fetch_ticker200_response.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/feed_fetch_tickers200_response.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/feed_list200_response.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/feed_load_markets200_response.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/feed_market.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/feed_oracle_round.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/feed_ticker.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/fetch_arbitrage200_response.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/fetch_arbitrage_params.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/fetch_balance200_response.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/fetch_event200_response.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/fetch_event_matches200_response.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/fetch_event_matches_params.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/fetch_events200_response.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/fetch_events_paginated200_response.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/fetch_market200_response.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/fetch_market_matches200_response.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/fetch_market_matches_params.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/fetch_markets200_response.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/fetch_markets_paginated200_response.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/fetch_matched_markets200_response.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/fetch_matched_markets_params.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/fetch_my_trades200_response.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/fetch_ohlcv200_response.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/fetch_open_orders200_response.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/fetch_order_book200_response.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/fetch_order_book_params.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/fetch_order_books200_response.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/fetch_order_books_request.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/fetch_positions200_response.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/fetch_trades200_response.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/filter_events_request.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/filter_events_request_args_inner.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/filter_markets_request.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/filter_markets_request_args_inner.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/get_execution_price200_response.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/get_execution_price_detailed200_response.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/get_execution_price_detailed_request.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/get_execution_price_request.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/get_execution_price_request_args_inner.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/health_check200_response.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/history_filter_params.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/load_markets200_response.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/load_markets_request.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/market_filter_criteria.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/market_filter_criteria_liquidity.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/market_filter_criteria_open_interest.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/market_filter_criteria_price.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/market_filter_criteria_resolution_date.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/market_filter_criteria_volume.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/market_filter_criteria_volume24h.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/market_filter_params.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/market_outcome.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/match_result.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/matched_market_pair.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/my_trades_params.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/ohlcv_params.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/order.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/order_book.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/order_history_params.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/order_level.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/paginated_events_result.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/paginated_markets_result.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/position.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/price_candle.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/price_comparison.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/submit_order_request.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/trade.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/trades_params.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/unified_event.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/unified_market.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/user_trade.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/py.typed +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/rest.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/pmxt/_exchanges.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/pmxt/_server/__init__.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/pmxt/_server/bin/pmxt-ensure-server +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/pmxt/_server/bin/pmxt-ensure-server.js +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/pmxt/constants.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/pmxt/errors.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/pmxt/feed_client.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/pmxt/models.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/pmxt/router.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/pmxt/server_manager.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/pmxt.egg-info/dependency_links.txt +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/pmxt.egg-info/requires.txt +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/pmxt.egg-info/top_level.txt +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/setup.cfg +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/tests/test_converters.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/tests/test_public_exports.py +0 -0
- {pmxt-2.46.2 → pmxt-2.46.4}/tests/test_sdk_integration.py +0 -0
|
@@ -91,7 +91,7 @@ class ApiClient:
|
|
|
91
91
|
self.default_headers[header_name] = header_value
|
|
92
92
|
self.cookie = cookie
|
|
93
93
|
# Set default User-Agent.
|
|
94
|
-
self.user_agent = 'OpenAPI-Generator/2.46.
|
|
94
|
+
self.user_agent = 'OpenAPI-Generator/2.46.4/python'
|
|
95
95
|
self.client_side_validation = configuration.client_side_validation
|
|
96
96
|
|
|
97
97
|
def __enter__(self):
|
|
@@ -506,7 +506,7 @@ class Configuration:
|
|
|
506
506
|
"OS: {env}\n"\
|
|
507
507
|
"Python Version: {pyversion}\n"\
|
|
508
508
|
"Version of the API: 0.4.4\n"\
|
|
509
|
-
"SDK Package Version: 2.46.
|
|
509
|
+
"SDK Package Version: 2.46.4".\
|
|
510
510
|
format(env=sys.platform, pyversion=sys.version)
|
|
511
511
|
|
|
512
512
|
def get_host_settings(self) -> List[HostSetting]:
|
|
@@ -214440,10 +214440,20 @@ var require_utils25 = __commonJS({
|
|
|
214440
214440
|
exports2.paginateLimitlessMarkets = paginateLimitlessMarkets;
|
|
214441
214441
|
var market_utils_1 = require_market_utils();
|
|
214442
214442
|
exports2.DEFAULT_LIMITLESS_API_URL = "https://api.limitless.exchange";
|
|
214443
|
-
function mapMarketToUnified(market) {
|
|
214443
|
+
function mapMarketToUnified(market, context = {}) {
|
|
214444
214444
|
if (!market)
|
|
214445
214445
|
return null;
|
|
214446
|
+
const resolvedContext = {
|
|
214447
|
+
eventId: getText(market.__pmxtEventId) || context.eventId,
|
|
214448
|
+
eventTitle: getText(market.__pmxtEventTitle) || context.eventTitle,
|
|
214449
|
+
eventDescription: getText(market.__pmxtEventDescription) || context.eventDescription,
|
|
214450
|
+
categories: getStringArray(market.__pmxtCategories) || context.categories,
|
|
214451
|
+
tags: getStringArray(market.__pmxtTags) || context.tags
|
|
214452
|
+
};
|
|
214446
214453
|
const outcomes = [];
|
|
214454
|
+
const rawTitle = getText(market.title) || getText(market.question) || market.slug;
|
|
214455
|
+
const title = composeMarketTitle(rawTitle, resolvedContext.eventTitle);
|
|
214456
|
+
const hasParentContext = Boolean(resolvedContext.eventTitle && rawTitle);
|
|
214447
214457
|
if (market.tokens) {
|
|
214448
214458
|
if (!market.tokens.yes || !market.tokens.no) {
|
|
214449
214459
|
throw new Error(`[limitless] Market "${market.slug}" is missing token addresses`);
|
|
@@ -214451,10 +214461,12 @@ var require_utils25 = __commonJS({
|
|
|
214451
214461
|
const prices = Array.isArray(market.prices) ? market.prices : [];
|
|
214452
214462
|
const yesPrice = prices[0] || 0;
|
|
214453
214463
|
const noPrice = prices[1] || 0;
|
|
214464
|
+
const yesLabel = hasParentContext ? rawTitle : "Yes";
|
|
214465
|
+
const noLabel = hasParentContext ? `Not ${rawTitle}` : "No";
|
|
214454
214466
|
outcomes.push({
|
|
214455
214467
|
outcomeId: market.tokens.yes,
|
|
214456
214468
|
marketId: market.slug,
|
|
214457
|
-
label:
|
|
214469
|
+
label: yesLabel,
|
|
214458
214470
|
price: yesPrice,
|
|
214459
214471
|
priceChange24h: 0,
|
|
214460
214472
|
metadata: { clobTokenId: market.tokens.yes }
|
|
@@ -214462,7 +214474,7 @@ var require_utils25 = __commonJS({
|
|
|
214462
214474
|
outcomes.push({
|
|
214463
214475
|
outcomeId: market.tokens.no,
|
|
214464
214476
|
marketId: market.slug,
|
|
214465
|
-
label:
|
|
214477
|
+
label: noLabel,
|
|
214466
214478
|
price: noPrice,
|
|
214467
214479
|
priceChange24h: 0,
|
|
214468
214480
|
metadata: { clobTokenId: market.tokens.no }
|
|
@@ -214476,9 +214488,9 @@ var require_utils25 = __commonJS({
|
|
|
214476
214488
|
const um = {
|
|
214477
214489
|
id: market.slug,
|
|
214478
214490
|
marketId: market.slug,
|
|
214479
|
-
eventId: market.slug,
|
|
214480
|
-
title
|
|
214481
|
-
description: market.description,
|
|
214491
|
+
eventId: resolvedContext.eventId || market.slug,
|
|
214492
|
+
title,
|
|
214493
|
+
description: market.description || resolvedContext.eventDescription,
|
|
214482
214494
|
slug: market.slug,
|
|
214483
214495
|
outcomes,
|
|
214484
214496
|
resolutionDate: market.expirationTimestamp ? new Date(market.expirationTimestamp) : /* @__PURE__ */ new Date(),
|
|
@@ -214490,13 +214502,32 @@ var require_utils25 = __commonJS({
|
|
|
214490
214502
|
// Not directly in the flat market list
|
|
214491
214503
|
url: `https://limitless.exchange/markets/${market.slug}`,
|
|
214492
214504
|
image: market.logo || `https://limitless.exchange/api/og?slug=${market.slug}`,
|
|
214493
|
-
category: market.categories?.[0],
|
|
214494
|
-
tags: market.tags || [],
|
|
214505
|
+
category: market.categories?.[0] || resolvedContext.categories?.[0],
|
|
214506
|
+
tags: market.tags || resolvedContext.tags || [],
|
|
214495
214507
|
status
|
|
214496
214508
|
};
|
|
214497
214509
|
(0, market_utils_1.addBinaryOutcomes)(um);
|
|
214498
214510
|
return um;
|
|
214499
214511
|
}
|
|
214512
|
+
function getText(value) {
|
|
214513
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
214514
|
+
}
|
|
214515
|
+
function getStringArray(value) {
|
|
214516
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : void 0;
|
|
214517
|
+
}
|
|
214518
|
+
function normalizeTitle(value) {
|
|
214519
|
+
return value.toLowerCase().replace(/[^\p{L}\p{N}]+/gu, " ").trim();
|
|
214520
|
+
}
|
|
214521
|
+
function composeMarketTitle(childTitle, eventTitle) {
|
|
214522
|
+
if (!eventTitle)
|
|
214523
|
+
return childTitle;
|
|
214524
|
+
const normalizedChild = normalizeTitle(childTitle);
|
|
214525
|
+
const normalizedEvent = normalizeTitle(eventTitle);
|
|
214526
|
+
if (!normalizedChild || !normalizedEvent || normalizedChild.startsWith(normalizedEvent)) {
|
|
214527
|
+
return childTitle;
|
|
214528
|
+
}
|
|
214529
|
+
return `${eventTitle} - ${childTitle}`;
|
|
214530
|
+
}
|
|
214500
214531
|
function mapIntervalToFidelity(interval) {
|
|
214501
214532
|
const mapping = {
|
|
214502
214533
|
"1m": 1,
|
|
@@ -214707,7 +214738,14 @@ var require_fetcher2 = __commonJS({
|
|
|
214707
214738
|
for (const res of rawResults) {
|
|
214708
214739
|
if (res.markets && Array.isArray(res.markets)) {
|
|
214709
214740
|
for (const child of res.markets) {
|
|
214710
|
-
allRawMarkets.push(
|
|
214741
|
+
allRawMarkets.push({
|
|
214742
|
+
...child,
|
|
214743
|
+
__pmxtEventId: res.slug,
|
|
214744
|
+
__pmxtEventTitle: res.title || res.question,
|
|
214745
|
+
__pmxtEventDescription: res.description,
|
|
214746
|
+
__pmxtCategories: res.categories,
|
|
214747
|
+
__pmxtTags: res.tags
|
|
214748
|
+
});
|
|
214711
214749
|
}
|
|
214712
214750
|
} else {
|
|
214713
214751
|
allRawMarkets.push(res);
|
|
@@ -214840,7 +214878,14 @@ var require_normalizer2 = __commonJS({
|
|
|
214840
214878
|
return null;
|
|
214841
214879
|
let marketsList = [];
|
|
214842
214880
|
if (raw.markets && Array.isArray(raw.markets)) {
|
|
214843
|
-
|
|
214881
|
+
const eventTitle = raw.title || raw.question || "";
|
|
214882
|
+
marketsList = raw.markets.map((child) => (0, utils_1.mapMarketToUnified)(child, {
|
|
214883
|
+
eventId: raw.slug,
|
|
214884
|
+
eventTitle,
|
|
214885
|
+
eventDescription: raw.description,
|
|
214886
|
+
categories: raw.categories,
|
|
214887
|
+
tags: raw.tags
|
|
214888
|
+
})).filter((m) => m !== null);
|
|
214844
214889
|
} else {
|
|
214845
214890
|
const unifiedMarket = (0, utils_1.mapMarketToUnified)(raw);
|
|
214846
214891
|
if (unifiedMarket)
|
|
@@ -276713,7 +276758,7 @@ var crypto_1 = require("crypto");
|
|
|
276713
276758
|
var crypto_2 = require("crypto");
|
|
276714
276759
|
var fs_1 = require("fs");
|
|
276715
276760
|
function getServerVersion() {
|
|
276716
|
-
const baseVersion = true ? "2.46.
|
|
276761
|
+
const baseVersion = true ? "2.46.4" : "0.0.0-dev";
|
|
276717
276762
|
const isDev = process.env.NODE_ENV === "development" || process.env.PMXT_ALWAYS_RESTART === "1" || __dirname.includes("/core/src/") || __dirname.includes("/core/dist/");
|
|
276718
276763
|
if (!isDev) {
|
|
276719
276764
|
return baseVersion;
|
|
@@ -1808,6 +1808,7 @@ class Exchange(ABC):
|
|
|
1808
1808
|
existing_id = ws._active_subs.pop(sub_key, None)
|
|
1809
1809
|
if existing_id:
|
|
1810
1810
|
ws._subscriptions.pop(existing_id, None)
|
|
1811
|
+
ws._data_queues.pop(existing_id, None)
|
|
1811
1812
|
ws._data_store.pop(existing_id, None)
|
|
1812
1813
|
except PmxtError:
|
|
1813
1814
|
raise
|
|
@@ -8,12 +8,42 @@ does not support the /ws endpoint.
|
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
import json
|
|
11
|
+
import socket
|
|
11
12
|
import threading
|
|
13
|
+
import time
|
|
12
14
|
import uuid
|
|
13
|
-
from
|
|
15
|
+
from collections import deque
|
|
16
|
+
from typing import Any, Deque, Dict, List, Optional
|
|
17
|
+
from urllib.parse import urlparse
|
|
14
18
|
|
|
15
19
|
from .errors import PmxtError
|
|
16
20
|
|
|
21
|
+
MAX_QUEUED_MESSAGES_PER_SUBSCRIPTION = 100_000
|
|
22
|
+
CONNECT_ATTEMPTS = 3
|
|
23
|
+
_NO_DATA = object()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _connect_websocket(ws: Any, url: str, timeout: float) -> None:
|
|
27
|
+
"""Connect, preferring IPv4 for hosted pmxt custom-domain websockets."""
|
|
28
|
+
host = urlparse(url).hostname
|
|
29
|
+
if host != "api.pmxt.dev":
|
|
30
|
+
ws.connect(url, timeout=timeout)
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
original_getaddrinfo = socket.getaddrinfo
|
|
34
|
+
|
|
35
|
+
def getaddrinfo_ipv4_first(*args: Any, **kwargs: Any) -> Any:
|
|
36
|
+
results = original_getaddrinfo(*args, **kwargs)
|
|
37
|
+
ipv4 = [item for item in results if item[0] == socket.AF_INET]
|
|
38
|
+
other = [item for item in results if item[0] != socket.AF_INET]
|
|
39
|
+
return ipv4 + other
|
|
40
|
+
|
|
41
|
+
socket.getaddrinfo = getaddrinfo_ipv4_first
|
|
42
|
+
try:
|
|
43
|
+
ws.connect(url, timeout=timeout)
|
|
44
|
+
finally:
|
|
45
|
+
socket.getaddrinfo = original_getaddrinfo
|
|
46
|
+
|
|
17
47
|
|
|
18
48
|
class _WsSubscription:
|
|
19
49
|
"""Tracks a single subscription and its pending data event."""
|
|
@@ -45,7 +75,9 @@ class SidecarWsClient:
|
|
|
45
75
|
self._reader_thread: Optional[threading.Thread] = None
|
|
46
76
|
self._closed = False
|
|
47
77
|
|
|
48
|
-
# request_id ->
|
|
78
|
+
# request_id -> queued data payloads for single-event watch methods
|
|
79
|
+
self._data_queues: Dict[str, Deque[Dict[str, Any]]] = {}
|
|
80
|
+
# request_id[:symbol] -> latest data payload for batch snapshots/errors
|
|
49
81
|
self._data_store: Dict[str, Dict[str, Any]] = {}
|
|
50
82
|
# request_id -> subscription metadata
|
|
51
83
|
self._subscriptions: Dict[str, _WsSubscription] = {}
|
|
@@ -85,8 +117,28 @@ class SidecarWsClient:
|
|
|
85
117
|
elif self._access_token:
|
|
86
118
|
url = f"{url}?token={self._access_token}"
|
|
87
119
|
|
|
88
|
-
|
|
89
|
-
ws
|
|
120
|
+
last_error: Optional[Exception] = None
|
|
121
|
+
ws = None
|
|
122
|
+
for attempt in range(CONNECT_ATTEMPTS):
|
|
123
|
+
ws = websocket.WebSocket()
|
|
124
|
+
try:
|
|
125
|
+
_connect_websocket(ws, url, timeout=10)
|
|
126
|
+
last_error = None
|
|
127
|
+
break
|
|
128
|
+
except Exception as exc:
|
|
129
|
+
last_error = exc
|
|
130
|
+
try:
|
|
131
|
+
ws.close()
|
|
132
|
+
except Exception:
|
|
133
|
+
pass
|
|
134
|
+
if attempt < CONNECT_ATTEMPTS - 1:
|
|
135
|
+
time.sleep(0.25 * (attempt + 1))
|
|
136
|
+
|
|
137
|
+
if last_error is not None:
|
|
138
|
+
raise last_error
|
|
139
|
+
if ws is None:
|
|
140
|
+
raise PmxtError("WebSocket connection failed")
|
|
141
|
+
ws.settimeout(None)
|
|
90
142
|
self._ws = ws
|
|
91
143
|
self._closed = False
|
|
92
144
|
|
|
@@ -117,10 +169,9 @@ class SidecarWsClient:
|
|
|
117
169
|
with self._lock:
|
|
118
170
|
for request_id, sub in list(self._subscriptions.items()):
|
|
119
171
|
if not sub.event.is_set():
|
|
120
|
-
self.
|
|
172
|
+
self._enqueue_data_locked(request_id, {
|
|
121
173
|
"_error": {"message": error_msg}
|
|
122
|
-
}
|
|
123
|
-
sub.event.set()
|
|
174
|
+
})
|
|
124
175
|
|
|
125
176
|
def _dispatch(self, msg: Dict[str, Any]) -> None:
|
|
126
177
|
"""Route an incoming server message to the matching subscription."""
|
|
@@ -129,10 +180,10 @@ class SidecarWsClient:
|
|
|
129
180
|
|
|
130
181
|
if event_type == "error":
|
|
131
182
|
# Wake up any waiter with the error stored
|
|
132
|
-
if request_id
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
183
|
+
if request_id:
|
|
184
|
+
with self._lock:
|
|
185
|
+
self._data_store[request_id] = {"_error": msg.get("error", {})}
|
|
186
|
+
self._enqueue_data_locked(request_id, {"_error": msg.get("error", {})})
|
|
136
187
|
return
|
|
137
188
|
|
|
138
189
|
if event_type == "subscribed":
|
|
@@ -142,14 +193,62 @@ class SidecarWsClient:
|
|
|
142
193
|
if event_type == "data" and request_id:
|
|
143
194
|
symbol = msg.get("symbol", "")
|
|
144
195
|
data = msg.get("data", {})
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
196
|
+
with self._lock:
|
|
197
|
+
# Store keyed by (request_id, symbol) for batch methods
|
|
198
|
+
store_key = f"{request_id}:{symbol}"
|
|
199
|
+
self._data_store[store_key] = data
|
|
200
|
+
self._enqueue_data_locked(request_id, data)
|
|
201
|
+
|
|
202
|
+
def _enqueue_data_locked(self, request_id: str, data: Dict[str, Any]) -> None:
|
|
203
|
+
queue = self._data_queues.setdefault(request_id, deque())
|
|
204
|
+
queue.append(data)
|
|
205
|
+
while len(queue) > MAX_QUEUED_MESSAGES_PER_SUBSCRIPTION:
|
|
206
|
+
queue.popleft()
|
|
207
|
+
|
|
208
|
+
sub = self._subscriptions.get(request_id)
|
|
209
|
+
if sub:
|
|
210
|
+
sub.event.set()
|
|
211
|
+
|
|
212
|
+
def _pop_data_locked(self, request_id: str) -> Any:
|
|
213
|
+
queue = self._data_queues.get(request_id)
|
|
214
|
+
if not queue:
|
|
215
|
+
return _NO_DATA
|
|
216
|
+
|
|
217
|
+
data = queue.popleft()
|
|
218
|
+
if not queue:
|
|
219
|
+
self._data_queues.pop(request_id, None)
|
|
220
|
+
sub = self._subscriptions.get(request_id)
|
|
221
|
+
if sub:
|
|
222
|
+
sub.event.clear()
|
|
223
|
+
return data
|
|
224
|
+
|
|
225
|
+
def _wait_for_subscription_data(
|
|
226
|
+
self,
|
|
227
|
+
sub: _WsSubscription,
|
|
228
|
+
timeout: float,
|
|
229
|
+
) -> Dict[str, Any]:
|
|
230
|
+
with self._lock:
|
|
231
|
+
data = self._pop_data_locked(sub.request_id)
|
|
232
|
+
if data is _NO_DATA:
|
|
233
|
+
sub.event.clear()
|
|
234
|
+
|
|
235
|
+
if data is _NO_DATA:
|
|
236
|
+
if not sub.event.wait(timeout=timeout):
|
|
237
|
+
raise PmxtError(f"Timeout waiting for WebSocket data (method={sub.method})")
|
|
238
|
+
|
|
239
|
+
with self._lock:
|
|
240
|
+
data = self._pop_data_locked(sub.request_id)
|
|
241
|
+
|
|
242
|
+
if data is _NO_DATA:
|
|
243
|
+
return {}
|
|
244
|
+
|
|
245
|
+
if isinstance(data, dict) and "_error" in data:
|
|
246
|
+
err = data["_error"]
|
|
247
|
+
raise PmxtError(
|
|
248
|
+
err.get("message", "WebSocket subscription error")
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
return data
|
|
153
252
|
|
|
154
253
|
# ------------------------------------------------------------------
|
|
155
254
|
# Public API
|
|
@@ -181,10 +280,6 @@ class SidecarWsClient:
|
|
|
181
280
|
existing_id = self._active_subs.get(sub_key)
|
|
182
281
|
if existing_id and existing_id in self._subscriptions:
|
|
183
282
|
sub = self._subscriptions[existing_id]
|
|
184
|
-
# Clear previous event so we wait for the NEXT push
|
|
185
|
-
sub.event.clear()
|
|
186
|
-
# Clear stale data
|
|
187
|
-
self._data_store.pop(existing_id, None)
|
|
188
283
|
else:
|
|
189
284
|
self._ensure_connected()
|
|
190
285
|
request_id = f"req-{uuid.uuid4().hex[:12]}"
|
|
@@ -207,19 +302,7 @@ class SidecarWsClient:
|
|
|
207
302
|
|
|
208
303
|
self._ws.send(json.dumps(message))
|
|
209
304
|
|
|
210
|
-
|
|
211
|
-
if not sub.event.wait(timeout=timeout):
|
|
212
|
-
raise PmxtError(f"Timeout waiting for WebSocket data (method={method})")
|
|
213
|
-
|
|
214
|
-
# Check for error
|
|
215
|
-
error_data = self._data_store.get(sub.request_id, {})
|
|
216
|
-
if isinstance(error_data, dict) and "_error" in error_data:
|
|
217
|
-
err = error_data["_error"]
|
|
218
|
-
raise PmxtError(
|
|
219
|
-
err.get("message", "WebSocket subscription error")
|
|
220
|
-
)
|
|
221
|
-
|
|
222
|
-
return self._data_store.get(sub.request_id, {})
|
|
305
|
+
return self._wait_for_subscription_data(sub, timeout)
|
|
223
306
|
|
|
224
307
|
def subscribe_batch(
|
|
225
308
|
self,
|
|
@@ -257,16 +340,7 @@ class SidecarWsClient:
|
|
|
257
340
|
|
|
258
341
|
# Wait for data event (the server may push one consolidated event
|
|
259
342
|
# or multiple per-symbol events)
|
|
260
|
-
|
|
261
|
-
raise PmxtError(f"Timeout waiting for WebSocket data (method={method})")
|
|
262
|
-
|
|
263
|
-
# Check for error
|
|
264
|
-
error_data = self._data_store.get(request_id, {})
|
|
265
|
-
if isinstance(error_data, dict) and "_error" in error_data:
|
|
266
|
-
err = error_data["_error"]
|
|
267
|
-
raise PmxtError(
|
|
268
|
-
err.get("message", "WebSocket subscription error")
|
|
269
|
-
)
|
|
343
|
+
first_data = self._wait_for_subscription_data(sub, timeout)
|
|
270
344
|
|
|
271
345
|
# Collect per-symbol data
|
|
272
346
|
result: Dict[str, Any] = {}
|
|
@@ -277,9 +351,7 @@ class SidecarWsClient:
|
|
|
277
351
|
# If no per-symbol data found, return the single data event
|
|
278
352
|
# (server may return a dict of all order books in one push)
|
|
279
353
|
if not result:
|
|
280
|
-
|
|
281
|
-
if isinstance(data, dict) and not data.get("_error"):
|
|
282
|
-
result = data
|
|
354
|
+
result = first_data
|
|
283
355
|
return result
|
|
284
356
|
|
|
285
357
|
def close(self) -> None:
|
|
@@ -292,6 +364,9 @@ class SidecarWsClient:
|
|
|
292
364
|
import logging
|
|
293
365
|
logging.warning("WebSocket close error: %s", e)
|
|
294
366
|
self._ws = None
|
|
367
|
+
with self._lock:
|
|
368
|
+
self._data_queues.clear()
|
|
369
|
+
self._data_store.clear()
|
|
295
370
|
|
|
296
371
|
@property
|
|
297
372
|
def connected(self) -> bool:
|
|
@@ -138,6 +138,7 @@ def test_unwatch_order_book_sends_websocket_unsubscribe(monkeypatch):
|
|
|
138
138
|
self._ws = FakeSocket()
|
|
139
139
|
self._active_subs = {"watchOrderBook:outcome-1": "req-existing"}
|
|
140
140
|
self._subscriptions = {"req-existing": object()}
|
|
141
|
+
self._data_queues = {"req-existing": [_raw_order_book()]}
|
|
141
142
|
self._data_store = {"req-existing": _raw_order_book()}
|
|
142
143
|
|
|
143
144
|
def _ensure_connected(self):
|
|
@@ -160,4 +161,5 @@ def test_unwatch_order_book_sends_websocket_unsubscribe(monkeypatch):
|
|
|
160
161
|
assert fake_ws._ws.sent[0]["args"] == ["outcome-1"]
|
|
161
162
|
assert "watchOrderBook:outcome-1" not in fake_ws._active_subs
|
|
162
163
|
assert "req-existing" not in fake_ws._subscriptions
|
|
164
|
+
assert "req-existing" not in fake_ws._data_queues
|
|
163
165
|
assert "req-existing" not in fake_ws._data_store
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
import time
|
|
3
|
+
import socket
|
|
4
|
+
import sys
|
|
5
|
+
import types
|
|
6
|
+
|
|
7
|
+
import pmxt.ws_client as ws_client
|
|
8
|
+
from pmxt.ws_client import SidecarWsClient, _WsSubscription, _connect_websocket
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _register_subscription(client, request_id="req-firehose"):
|
|
12
|
+
sub = _WsSubscription(request_id, "watchAllOrderBooks", [])
|
|
13
|
+
with client._lock:
|
|
14
|
+
client._subscriptions[request_id] = sub
|
|
15
|
+
client._active_subs["watchAllOrderBooks:"] = request_id
|
|
16
|
+
return sub
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_queues_repeated_data_events_in_fifo_order():
|
|
20
|
+
client = SidecarWsClient("http://localhost:3847")
|
|
21
|
+
_register_subscription(client)
|
|
22
|
+
|
|
23
|
+
client._dispatch({"event": "data", "id": "req-firehose", "symbol": "a", "data": {"sequence": 1}})
|
|
24
|
+
client._dispatch({"event": "data", "id": "req-firehose", "symbol": "b", "data": {"sequence": 2}})
|
|
25
|
+
client._dispatch({"event": "data", "id": "req-firehose", "symbol": "c", "data": {"sequence": 3}})
|
|
26
|
+
|
|
27
|
+
assert client.subscribe("mock", "watchAllOrderBooks", [], timeout=0.1) == {"sequence": 1}
|
|
28
|
+
assert client.subscribe("mock", "watchAllOrderBooks", [], timeout=0.1) == {"sequence": 2}
|
|
29
|
+
assert client.subscribe("mock", "watchAllOrderBooks", [], timeout=0.1) == {"sequence": 3}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_resolves_pending_waiter_and_queues_later_events():
|
|
33
|
+
client = SidecarWsClient("http://localhost:3847")
|
|
34
|
+
_register_subscription(client, "req-pending")
|
|
35
|
+
result = []
|
|
36
|
+
|
|
37
|
+
thread = threading.Thread(
|
|
38
|
+
target=lambda: result.append(client.subscribe("mock", "watchAllOrderBooks", [], timeout=1.0))
|
|
39
|
+
)
|
|
40
|
+
thread.start()
|
|
41
|
+
time.sleep(0.05)
|
|
42
|
+
|
|
43
|
+
client._dispatch({"event": "data", "id": "req-pending", "symbol": "a", "data": {"sequence": 1}})
|
|
44
|
+
client._dispatch({"event": "data", "id": "req-pending", "symbol": "b", "data": {"sequence": 2}})
|
|
45
|
+
|
|
46
|
+
thread.join(timeout=1.0)
|
|
47
|
+
assert result == [{"sequence": 1}]
|
|
48
|
+
assert client.subscribe("mock", "watchAllOrderBooks", [], timeout=0.1) == {"sequence": 2}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_close_clears_queued_events():
|
|
52
|
+
client = SidecarWsClient("http://localhost:3847")
|
|
53
|
+
_register_subscription(client, "req-close")
|
|
54
|
+
|
|
55
|
+
client._dispatch({"event": "data", "id": "req-close", "symbol": "a", "data": {"sequence": 1}})
|
|
56
|
+
client._dispatch({"event": "data", "id": "req-close", "symbol": "b", "data": {"sequence": 2}})
|
|
57
|
+
|
|
58
|
+
assert len(client._data_queues["req-close"]) == 2
|
|
59
|
+
client.close()
|
|
60
|
+
assert "req-close" not in client._data_queues
|
|
61
|
+
assert client._data_store == {}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_drops_oldest_queued_events_after_cap():
|
|
65
|
+
client = SidecarWsClient("http://localhost:3847")
|
|
66
|
+
_register_subscription(client, "req-overflow")
|
|
67
|
+
|
|
68
|
+
for sequence in range(1, 100_002):
|
|
69
|
+
client._dispatch({
|
|
70
|
+
"event": "data",
|
|
71
|
+
"id": "req-overflow",
|
|
72
|
+
"symbol": str(sequence),
|
|
73
|
+
"data": {"sequence": sequence},
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
assert len(client._data_queues["req-overflow"]) == 100_000
|
|
77
|
+
assert client.subscribe("mock", "watchAllOrderBooks", [], timeout=0.1) == {"sequence": 2}
|
|
78
|
+
assert client.subscribe("mock", "watchAllOrderBooks", [], timeout=0.1) == {"sequence": 3}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def test_hosted_websocket_connect_prefers_ipv4(monkeypatch):
|
|
82
|
+
original = socket.getaddrinfo
|
|
83
|
+
|
|
84
|
+
def fake_getaddrinfo(*_args, **_kwargs):
|
|
85
|
+
return [
|
|
86
|
+
(socket.AF_INET6, None, None, None, ("::1", 443, 0, 0)),
|
|
87
|
+
(socket.AF_INET, None, None, None, ("127.0.0.1", 443)),
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
observed = {}
|
|
91
|
+
|
|
92
|
+
class FakeWebSocket:
|
|
93
|
+
def connect(self, url, timeout):
|
|
94
|
+
observed["url"] = url
|
|
95
|
+
observed["timeout"] = timeout
|
|
96
|
+
observed["families"] = [item[0] for item in socket.getaddrinfo("api.pmxt.dev", 443)]
|
|
97
|
+
|
|
98
|
+
monkeypatch.setattr(socket, "getaddrinfo", fake_getaddrinfo)
|
|
99
|
+
|
|
100
|
+
_connect_websocket(FakeWebSocket(), "wss://api.pmxt.dev/ws?apiKey=test", timeout=10)
|
|
101
|
+
|
|
102
|
+
assert observed["url"] == "wss://api.pmxt.dev/ws?apiKey=test"
|
|
103
|
+
assert observed["timeout"] == 10
|
|
104
|
+
assert observed["families"] == [socket.AF_INET, socket.AF_INET6]
|
|
105
|
+
assert socket.getaddrinfo is fake_getaddrinfo
|
|
106
|
+
monkeypatch.setattr(socket, "getaddrinfo", original)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_connect_retries_transient_handshake_failures(monkeypatch):
|
|
110
|
+
attempts = {"count": 0}
|
|
111
|
+
|
|
112
|
+
class FakeWebSocket:
|
|
113
|
+
def settimeout(self, _timeout):
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
def close(self):
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
monkeypatch.setitem(sys.modules, "websocket", types.SimpleNamespace(WebSocket=FakeWebSocket))
|
|
120
|
+
|
|
121
|
+
def fake_connect(_ws, _url, timeout):
|
|
122
|
+
assert timeout == 10
|
|
123
|
+
attempts["count"] += 1
|
|
124
|
+
if attempts["count"] < 3:
|
|
125
|
+
raise OSError("transient handshake failure")
|
|
126
|
+
|
|
127
|
+
monkeypatch.setattr(ws_client, "_connect_websocket", fake_connect)
|
|
128
|
+
monkeypatch.setattr(ws_client.time, "sleep", lambda _seconds: None)
|
|
129
|
+
|
|
130
|
+
client = SidecarWsClient("https://api.pmxt.dev", api_key="pmxt_test")
|
|
131
|
+
with client._lock:
|
|
132
|
+
client._ensure_connected()
|
|
133
|
+
|
|
134
|
+
assert attempts["count"] == 3
|
|
135
|
+
assert client._ws is not None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/compare_market_prices200_response.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/event_filter_criteria_total_volume.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/exchange_credentials_signature_type.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/feed_fetch_oracle_history200_response.py
RENAMED
|
File without changes
|
{pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/feed_fetch_oracle_round200_response.py
RENAMED
|
File without changes
|
{pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/feed_fetch_order_book200_response.py
RENAMED
|
File without changes
|
{pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/feed_fetch_order_book200_response_data.py
RENAMED
|
File without changes
|
|
File without changes
|
{pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/feed_fetch_tickers200_response.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/fetch_event_matches200_response.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/fetch_events_paginated200_response.py
RENAMED
|
File without changes
|
|
File without changes
|
{pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/fetch_market_matches200_response.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/fetch_markets_paginated200_response.py
RENAMED
|
File without changes
|
{pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/fetch_matched_markets200_response.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/filter_events_request_args_inner.py
RENAMED
|
File without changes
|
|
File without changes
|
{pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/filter_markets_request_args_inner.py
RENAMED
|
File without changes
|
{pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/get_execution_price200_response.py
RENAMED
|
File without changes
|
|
File without changes
|
{pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/get_execution_price_detailed_request.py
RENAMED
|
File without changes
|
|
File without changes
|
{pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/get_execution_price_request_args_inner.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/market_filter_criteria_liquidity.py
RENAMED
|
File without changes
|
{pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/market_filter_criteria_open_interest.py
RENAMED
|
File without changes
|
|
File without changes
|
{pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/market_filter_criteria_resolution_date.py
RENAMED
|
File without changes
|
|
File without changes
|
{pmxt-2.46.2 → pmxt-2.46.4}/generated/pmxt_internal/models/market_filter_criteria_volume24h.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|