unrealon 2.0.11__py3-none-any.whl → 2.0.13__py3-none-any.whl
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.
- {unrealon-2.0.11.dist-info → unrealon-2.0.13.dist-info}/METADATA +1 -1
- {unrealon-2.0.11.dist-info → unrealon-2.0.13.dist-info}/RECORD +13 -12
- unrealon_browser/core/browser_manager.py +58 -0
- unrealon_driver/__init__.py +12 -2
- unrealon_driver/driver/factory/manager_factory.py +8 -1
- unrealon_driver/managers/browser.py +16 -1
- unrealon_driver/managers/proxy.py +11 -4
- unrealon_driver/utils/__init__.py +13 -4
- unrealon_driver/utils/platform_compatibility.py +205 -0
- {unrealon-2.0.11.dist-info → unrealon-2.0.13.dist-info}/LICENSE +0 -0
- {unrealon-2.0.11.dist-info → unrealon-2.0.13.dist-info}/WHEEL +0 -0
- {unrealon-2.0.11.dist-info → unrealon-2.0.13.dist-info}/entry_points.txt +0 -0
- {unrealon-2.0.11.dist-info → unrealon-2.0.13.dist-info}/top_level.txt +0 -0
|
@@ -6,7 +6,7 @@ unrealon_browser/cli/cookies_cli.py,sha256=yhZvGrg8bknlH4zlySdi8ue-25Ue-1rI_u1G0
|
|
|
6
6
|
unrealon_browser/cli/interactive_mode.py,sha256=gLn9bMH0h0tPX3dP4i4QQxQK4Htkyg5r4KcqdMBaP6Q,12125
|
|
7
7
|
unrealon_browser/cli/main.py,sha256=XCYcTxJUqaz320KCU_JPKizYMk6bdljb8Boyok3uO-4,1353
|
|
8
8
|
unrealon_browser/core/__init__.py,sha256=uVL_t4sZelUzflWPdgrwoXGnAkSV1WNQ98-eu0QB2eM,151
|
|
9
|
-
unrealon_browser/core/browser_manager.py,sha256=
|
|
9
|
+
unrealon_browser/core/browser_manager.py,sha256=vPWQjoh_QAoKWFlKVQCPIuehNsWIY9HIN7z3QeGoxAc,32398
|
|
10
10
|
unrealon_browser/dto/__init__.py,sha256=bApqcLz-KanEi0_MCiFPrQmGBoX3VBijP7XtBUyIfjo,1636
|
|
11
11
|
unrealon_browser/dto/bot_detection.py,sha256=qXfC0HghV7m4L6qA87t3STi-166jM-QgoP6OYbCb4o4,6884
|
|
12
12
|
unrealon_browser/dto/models/config.py,sha256=Why5H3rtFclmwbdczuDfhlgf-LDz72Aa8LhDX4_ayfw,1752
|
|
@@ -83,7 +83,7 @@ unrealon_core/monitoring/health_check.py,sha256=UAdZ1Of0LCWFVfVXQP0BF-4HDOe4rxyh
|
|
|
83
83
|
unrealon_core/monitoring/metrics.py,sha256=UPCRoridgGVxQ1kh--NuXzH-2dY0WyKljfXDH96OroU,11705
|
|
84
84
|
unrealon_core/utils/__init__.py,sha256=L4nJum_ekqh3Rn324pcIASfr6QQtnT_PwWKCtl2L0mU,179
|
|
85
85
|
unrealon_core/utils/time.py,sha256=27iWhOEl4wHgBOag7gqFlI0hl5cTHPnKHoe8GxmnKHA,1356
|
|
86
|
-
unrealon_driver/__init__.py,sha256=
|
|
86
|
+
unrealon_driver/__init__.py,sha256=0uwrmIEaAwpOSSTd6nv8fJinhpW2whBQHvqN430Cbz0,2130
|
|
87
87
|
unrealon_driver/core_module/__init__.py,sha256=v4bHF0eqSQBAUBBEl8RnQWrvb3LjAeIAQCeIXrRTy3w,620
|
|
88
88
|
unrealon_driver/core_module/base.py,sha256=RbC6MVQSA-1145gnDMdsMJ_QnMpQT52pIg86UfRSSMc,6135
|
|
89
89
|
unrealon_driver/core_module/config.py,sha256=N0XGWsoYmv2ruKZOZKOyfaHOZ_jstUDqH7aTqURG2TI,1023
|
|
@@ -103,7 +103,7 @@ unrealon_driver/driver/core/__init__.py,sha256=ZvJQp1zO7pj6tBNYTJk2fj-0ZMiQTQEk-
|
|
|
103
103
|
unrealon_driver/driver/core/config.py,sha256=jWJjRll19VlL4iM5Q-J3o9qwYeH89Iuj1_3KayM6fCk,5914
|
|
104
104
|
unrealon_driver/driver/core/driver.py,sha256=NI-pdhnduRyHLsfFr8HmP2gp7pR1pWB4vBIJkMJ2cls,7886
|
|
105
105
|
unrealon_driver/driver/factory/__init__.py,sha256=XrjBhOaLvC3MIG5PAFIYS_xYXFDz5JizpFvmQcwA7mU,189
|
|
106
|
-
unrealon_driver/driver/factory/manager_factory.py,sha256=
|
|
106
|
+
unrealon_driver/driver/factory/manager_factory.py,sha256=b-3tWKsnicTNygZ3zIDhBlSbGySPRPT1GYrFY32QQgo,5103
|
|
107
107
|
unrealon_driver/driver/lifecycle/__init__.py,sha256=KnkXklezAOIbXcCzEU_XSOt32z7tz1zIGclXYXTkO8k,286
|
|
108
108
|
unrealon_driver/driver/lifecycle/daemon.py,sha256=KHAzpiWFu3HRElRtzSEStmI74bMivFjfCAFlXha87KU,2609
|
|
109
109
|
unrealon_driver/driver/lifecycle/initialization.py,sha256=R4MgfkSNnfAdMO0Kp1Cx42cfNqq8VIxj_mGX7ECXad4,4406
|
|
@@ -115,19 +115,20 @@ unrealon_driver/driver/utilities/logging.py,sha256=2my2QnkAa6Hdw-TfO4oOQ94yGc-Cj
|
|
|
115
115
|
unrealon_driver/driver/utilities/serialization.py,sha256=wTCSVrEloykiGN4K1JXbk2aqNKm7W90aWXmzhcLyAZc,2123
|
|
116
116
|
unrealon_driver/managers/__init__.py,sha256=zJJsOb6Oodg7l00v4ncKUytnyeaZM887pHY8-eSuWdU,981
|
|
117
117
|
unrealon_driver/managers/base.py,sha256=GkuXillg9uqqnx6RL682fmKgK-7JyqYlH6DFUgyN4F8,5445
|
|
118
|
-
unrealon_driver/managers/browser.py,sha256=
|
|
118
|
+
unrealon_driver/managers/browser.py,sha256=bc6O2NyC4FV82mb9sat48_k8s1c3IGY4i90ddMVWRIo,5432
|
|
119
119
|
unrealon_driver/managers/cache.py,sha256=c0tPKQ5KFd_Un1U8mw3j1WPuycxg863MMWNMveVF_2I,3506
|
|
120
120
|
unrealon_driver/managers/http.py,sha256=EjlpoTRuhpsgzzrEARxRlbGczzua7hnKFVq06bvCgTM,3624
|
|
121
121
|
unrealon_driver/managers/logger.py,sha256=PL3rA9ZQl12jJU0EiPAkLwJ6eDHQfIzr8-nc8bVivKQ,10526
|
|
122
|
-
unrealon_driver/managers/proxy.py,sha256=
|
|
122
|
+
unrealon_driver/managers/proxy.py,sha256=b2w6DteMJWnwxZmL3NfwBMdE_mscchoMwPs-XFKNwnU,3855
|
|
123
123
|
unrealon_driver/managers/registry.py,sha256=--oNPU-65e8J21ubJufyEOc1TirnzJIvpvuY_j7rH7Q,2666
|
|
124
124
|
unrealon_driver/managers/threading.py,sha256=djw5cSC99dfBKmep3IJ_8IgxQceMXtNvCp5fIxHM0TY,1702
|
|
125
125
|
unrealon_driver/managers/update.py,sha256=-hohVxGXpj5bZ6ZTQN6NH1RK9Pd6GVzCMtu3GS2SdcQ,3582
|
|
126
|
-
unrealon_driver/utils/__init__.py,sha256=
|
|
126
|
+
unrealon_driver/utils/__init__.py,sha256=qMEFiXU7R8hMZ9yAgUYMZ_TF-Yqo3w9Xm7ToPO9Pew0,383
|
|
127
|
+
unrealon_driver/utils/platform_compatibility.py,sha256=kydNJhxswBq-z249YQBBVxP0QKEP3vQHuvGuYcBVH-w,7617
|
|
127
128
|
unrealon_driver/utils/time.py,sha256=Oxk1eicKeZl8ZWbf7gu1Ll716k6CpXmVj67FHSnPIsA,184
|
|
128
|
-
unrealon-2.0.
|
|
129
|
-
unrealon-2.0.
|
|
130
|
-
unrealon-2.0.
|
|
131
|
-
unrealon-2.0.
|
|
132
|
-
unrealon-2.0.
|
|
133
|
-
unrealon-2.0.
|
|
129
|
+
unrealon-2.0.13.dist-info/LICENSE,sha256=eEH8mWZW49YMpl4Sh5MtKqkZ8aVTzKQXiNPEnvL14ns,1070
|
|
130
|
+
unrealon-2.0.13.dist-info/METADATA,sha256=A051dpkf4-8dQevRVKFvOwHfvBQy3UPytnQ9k0w5cbQ,15689
|
|
131
|
+
unrealon-2.0.13.dist-info/WHEEL,sha256=pL8R0wFFS65tNSRnaOVrsw9EOkOqxLrlUPenUYnJKNo,91
|
|
132
|
+
unrealon-2.0.13.dist-info/entry_points.txt,sha256=k0qM-eotpajkKUq-almJmxj9afhXprZ6IkvQkSdcKhI,104
|
|
133
|
+
unrealon-2.0.13.dist-info/top_level.txt,sha256=Gu8IeIfIVfUxdi-h-F0nKMQxo15pjhHZ0aTadXTpRE8,47
|
|
134
|
+
unrealon-2.0.13.dist-info/RECORD,,
|
|
@@ -55,6 +55,9 @@ class BrowserManager:
|
|
|
55
55
|
self._page = None
|
|
56
56
|
self._initialized = False
|
|
57
57
|
self._statistics = BrowserManagerStatistics()
|
|
58
|
+
|
|
59
|
+
# Proxy settings (can be set externally)
|
|
60
|
+
self._proxy_url: Optional[str] = None
|
|
58
61
|
|
|
59
62
|
# Initialize logger bridge first
|
|
60
63
|
self.logger_bridge = create_browser_logger_bridge(session_id=self._generate_session_id(), parser_id=self.parser_id, enable_console=True) # Use resolved parser_id
|
|
@@ -197,6 +200,12 @@ class BrowserManager:
|
|
|
197
200
|
"ignore_default_args": ["--enable-automation"],
|
|
198
201
|
**context_options, # viewport, user_agent, etc.
|
|
199
202
|
}
|
|
203
|
+
|
|
204
|
+
# Add proxy configuration for persistent context
|
|
205
|
+
if self._proxy_url:
|
|
206
|
+
proxy_config = self._get_playwright_proxy_config()
|
|
207
|
+
if proxy_config:
|
|
208
|
+
persistent_args["proxy"] = proxy_config
|
|
200
209
|
|
|
201
210
|
# Use persistent context with user_data_dir for profiles
|
|
202
211
|
if self.config.browser_type == BrowserType.CHROMIUM:
|
|
@@ -223,6 +232,12 @@ class BrowserManager:
|
|
|
223
232
|
raise ValueError(f"Unsupported browser type: {self.config.browser_type}")
|
|
224
233
|
|
|
225
234
|
# Create context without profile
|
|
235
|
+
# Add proxy configuration for context
|
|
236
|
+
if self._proxy_url:
|
|
237
|
+
proxy_config = self._get_playwright_proxy_config()
|
|
238
|
+
if proxy_config:
|
|
239
|
+
context_options["proxy"] = proxy_config
|
|
240
|
+
|
|
226
241
|
self._context = await self._browser.new_context(**context_options)
|
|
227
242
|
|
|
228
243
|
# 🔥 STEALTH ALWAYS ON - NO CONFIG NEEDED!
|
|
@@ -276,6 +291,9 @@ class BrowserManager:
|
|
|
276
291
|
# 🔥 STEALTH ALWAYS ON - ALWAYS ADD STEALTH ARGS!
|
|
277
292
|
browser_args.extend(self.stealth_manager.get_stealth_args())
|
|
278
293
|
|
|
294
|
+
# Note: Proxy configuration is now handled via Playwright API in context options
|
|
295
|
+
# instead of command line arguments for better compatibility
|
|
296
|
+
|
|
279
297
|
if self.config.disable_images:
|
|
280
298
|
browser_args.extend(
|
|
281
299
|
[
|
|
@@ -286,6 +304,46 @@ class BrowserManager:
|
|
|
286
304
|
|
|
287
305
|
args["args"] = browser_args
|
|
288
306
|
return args
|
|
307
|
+
|
|
308
|
+
def _get_playwright_proxy_config(self) -> Optional[Dict[str, Any]]:
|
|
309
|
+
"""
|
|
310
|
+
Get Playwright-compatible proxy configuration.
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
Proxy configuration dict for Playwright or None if not supported
|
|
314
|
+
"""
|
|
315
|
+
if not self._proxy_url:
|
|
316
|
+
return None
|
|
317
|
+
|
|
318
|
+
try:
|
|
319
|
+
from urllib.parse import urlparse
|
|
320
|
+
|
|
321
|
+
parsed = urlparse(self._proxy_url)
|
|
322
|
+
|
|
323
|
+
# Playwright proxy configuration
|
|
324
|
+
proxy_config = {
|
|
325
|
+
"server": f"{parsed.scheme}://{parsed.hostname}:{parsed.port}"
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
# Add authentication if present
|
|
329
|
+
if parsed.username and parsed.password:
|
|
330
|
+
# WARNING: Playwright does NOT support SOCKS5 authentication!
|
|
331
|
+
if parsed.scheme == 'socks5':
|
|
332
|
+
self.logger_bridge.log_warning("⚠️ Playwright does not support SOCKS5 authentication!")
|
|
333
|
+
self.logger_bridge.log_warning("⚠️ Falling back to SOCKS5 without auth - may fail!")
|
|
334
|
+
# Return configuration without authentication for SOCKS5
|
|
335
|
+
return proxy_config
|
|
336
|
+
else:
|
|
337
|
+
# HTTP/HTTPS proxies support authentication
|
|
338
|
+
proxy_config["username"] = parsed.username
|
|
339
|
+
proxy_config["password"] = parsed.password
|
|
340
|
+
|
|
341
|
+
self.logger_bridge.log_info(f"🔒 Playwright proxy config: {proxy_config['server']}")
|
|
342
|
+
return proxy_config
|
|
343
|
+
|
|
344
|
+
except Exception as e:
|
|
345
|
+
self.logger_bridge.log_error(f"❌ Failed to parse proxy URL: {e}")
|
|
346
|
+
return None
|
|
289
347
|
|
|
290
348
|
def _get_context_options(self) -> Dict[str, Any]:
|
|
291
349
|
"""Get browser context options"""
|
unrealon_driver/__init__.py
CHANGED
|
@@ -60,15 +60,20 @@ from .decorators import (
|
|
|
60
60
|
timing
|
|
61
61
|
)
|
|
62
62
|
|
|
63
|
+
# Utilities - Cross-platform compatibility
|
|
64
|
+
from .utils import (
|
|
65
|
+
PlatformCompatibility,
|
|
66
|
+
ensure_platform_compatibility,
|
|
67
|
+
get_platform_info
|
|
68
|
+
)
|
|
69
|
+
|
|
63
70
|
__version__ = get_driver_version()
|
|
64
71
|
__author__ = "UnrealOn Team"
|
|
65
|
-
__phase__ = "Phase 3.8: Clean Refactor"
|
|
66
72
|
|
|
67
73
|
__all__ = [
|
|
68
74
|
# Version
|
|
69
75
|
"__version__",
|
|
70
76
|
"__author__",
|
|
71
|
-
"__phase__",
|
|
72
77
|
|
|
73
78
|
# Core API
|
|
74
79
|
"UniversalDriver",
|
|
@@ -102,4 +107,9 @@ __all__ = [
|
|
|
102
107
|
"retry",
|
|
103
108
|
"schedule",
|
|
104
109
|
"timing",
|
|
110
|
+
|
|
111
|
+
# Utilities
|
|
112
|
+
"PlatformCompatibility",
|
|
113
|
+
"ensure_platform_compatibility",
|
|
114
|
+
"get_platform_info",
|
|
105
115
|
]
|
|
@@ -81,11 +81,18 @@ class ManagerFactory:
|
|
|
81
81
|
@staticmethod
|
|
82
82
|
def _setup_browser_manager(driver: 'UniversalDriver', registry: ManagerRegistry):
|
|
83
83
|
"""Setup browser manager."""
|
|
84
|
+
# Get proxy URL from proxy manager if enabled
|
|
85
|
+
proxy_url = None
|
|
86
|
+
if driver.config.proxy_enabled and hasattr(driver, 'proxy') and driver.proxy:
|
|
87
|
+
proxy_url = driver.proxy.get_proxy()
|
|
88
|
+
|
|
84
89
|
browser_config = BrowserManagerConfig(
|
|
85
90
|
enabled=True,
|
|
86
91
|
headless=driver.config.browser_headless,
|
|
87
92
|
timeout=driver.config.browser_timeout,
|
|
88
|
-
parser_name=driver.driver_id
|
|
93
|
+
parser_name=driver.driver_id,
|
|
94
|
+
proxy_enabled=driver.config.proxy_enabled,
|
|
95
|
+
proxy_url=proxy_url
|
|
89
96
|
)
|
|
90
97
|
driver.browser = BrowserManager(browser_config)
|
|
91
98
|
registry.register(driver.browser)
|
|
@@ -17,6 +17,11 @@ class BrowserManagerConfig(ManagerConfig):
|
|
|
17
17
|
headless: bool = Field(default=True, description="Run headless")
|
|
18
18
|
parser_name: str = Field(..., description="Parser name for browser")
|
|
19
19
|
stealth_warmup_enabled: bool = Field(default=False, description="Enable stealth warmup")
|
|
20
|
+
|
|
21
|
+
# Proxy settings
|
|
22
|
+
proxy_enabled: bool = Field(default=False, description="Enable proxy usage")
|
|
23
|
+
proxy_url: Optional[str] = Field(default=None, description="Proxy URL (http://user:pass@host:port)")
|
|
24
|
+
proxy_timeout: int = Field(default=30, description="Proxy timeout in seconds")
|
|
20
25
|
|
|
21
26
|
|
|
22
27
|
class BrowserManager(BaseManager):
|
|
@@ -53,6 +58,12 @@ class BrowserManager(BaseManager):
|
|
|
53
58
|
try:
|
|
54
59
|
self.logger.info("🚀 Starting browser (lazy initialization)...")
|
|
55
60
|
|
|
61
|
+
# Log proxy status
|
|
62
|
+
if self.config.proxy_enabled and self.config.proxy_url:
|
|
63
|
+
self.logger.info(f"🔒 Proxy enabled: {self.config.proxy_url}")
|
|
64
|
+
else:
|
|
65
|
+
self.logger.info("🌐 No proxy configured")
|
|
66
|
+
|
|
56
67
|
# Create browser config with proper headless mode
|
|
57
68
|
browser_mode = BrowserMode.HEADLESS if self.config.headless else BrowserMode.HEADED
|
|
58
69
|
|
|
@@ -62,8 +73,12 @@ class BrowserManager(BaseManager):
|
|
|
62
73
|
stealth_warmup_enabled=self.config.stealth_warmup_enabled
|
|
63
74
|
)
|
|
64
75
|
|
|
65
|
-
# Create browser manager
|
|
76
|
+
# Create browser manager with proxy support
|
|
66
77
|
self.browser = CoreBrowserManager(browser_config)
|
|
78
|
+
|
|
79
|
+
# Set proxy if enabled (we'll need to modify CoreBrowserManager to support this)
|
|
80
|
+
if self.config.proxy_enabled and self.config.proxy_url:
|
|
81
|
+
self.browser._proxy_url = self.config.proxy_url
|
|
67
82
|
|
|
68
83
|
# Initialize browser
|
|
69
84
|
await self.browser.initialize_async()
|
|
@@ -13,6 +13,7 @@ from .base import BaseManager, ManagerConfig
|
|
|
13
13
|
class ProxyManagerConfig(ManagerConfig):
|
|
14
14
|
"""Proxy manager configuration."""
|
|
15
15
|
proxies: List[str] = Field(default_factory=list, description="List of proxy URLs")
|
|
16
|
+
single_proxy: Optional[str] = Field(default=None, description="Single proxy URL to use")
|
|
16
17
|
rotation_interval: int = Field(default=300, description="Rotation interval seconds")
|
|
17
18
|
health_check_interval: int = Field(default=60, description="Health check interval")
|
|
18
19
|
|
|
@@ -29,11 +30,17 @@ class ProxyManager(BaseManager):
|
|
|
29
30
|
|
|
30
31
|
async def _initialize(self) -> bool:
|
|
31
32
|
"""Initialize proxy manager."""
|
|
32
|
-
|
|
33
|
+
# Use single proxy if specified, otherwise use proxy list
|
|
34
|
+
if self.config.single_proxy:
|
|
35
|
+
self.active_proxies = [self.config.single_proxy]
|
|
36
|
+
self.current_proxy = self.config.single_proxy
|
|
37
|
+
else:
|
|
38
|
+
self.active_proxies = self.config.proxies.copy()
|
|
39
|
+
if self.active_proxies:
|
|
40
|
+
self.current_proxy = random.choice(self.active_proxies)
|
|
33
41
|
|
|
34
|
-
if
|
|
35
|
-
|
|
36
|
-
# Start rotation task
|
|
42
|
+
# Only start rotation if we have multiple proxies
|
|
43
|
+
if len(self.active_proxies) > 1:
|
|
37
44
|
self._rotation_task = asyncio.create_task(self._rotation_loop())
|
|
38
45
|
|
|
39
46
|
return True
|
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Utility
|
|
3
|
-
"""
|
|
2
|
+
Utility modules for UnrealOn Driver.
|
|
4
3
|
|
|
4
|
+
Contains cross-platform compatibility and other utility functions.
|
|
5
|
+
"""
|
|
5
6
|
from .time import utc_now
|
|
7
|
+
from .platform_compatibility import (
|
|
8
|
+
PlatformCompatibility,
|
|
9
|
+
ensure_platform_compatibility,
|
|
10
|
+
get_platform_info
|
|
11
|
+
)
|
|
6
12
|
|
|
7
13
|
__all__ = [
|
|
8
|
-
|
|
9
|
-
|
|
14
|
+
'utc_now',
|
|
15
|
+
'PlatformCompatibility',
|
|
16
|
+
'ensure_platform_compatibility',
|
|
17
|
+
'get_platform_info'
|
|
18
|
+
]
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cross-platform compatibility utilities for UnrealOn Driver.
|
|
3
|
+
|
|
4
|
+
Handles platform-specific configurations and fixes for Windows, macOS, and Linux.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import sys
|
|
9
|
+
import platform
|
|
10
|
+
import warnings
|
|
11
|
+
from typing import Optional
|
|
12
|
+
import logging
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PlatformCompatibility:
|
|
18
|
+
"""
|
|
19
|
+
Handles cross-platform compatibility for UnrealOn Driver.
|
|
20
|
+
|
|
21
|
+
Automatically applies platform-specific fixes and configurations
|
|
22
|
+
to ensure consistent behavior across Windows, macOS, and Linux.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self):
|
|
26
|
+
"""Initialize platform compatibility."""
|
|
27
|
+
self.platform = platform.system()
|
|
28
|
+
self.python_version = sys.version_info
|
|
29
|
+
self._applied_fixes = []
|
|
30
|
+
|
|
31
|
+
def apply_all_fixes(self) -> None:
|
|
32
|
+
"""
|
|
33
|
+
Apply all platform-specific fixes automatically.
|
|
34
|
+
|
|
35
|
+
This method should be called once at the start of the application
|
|
36
|
+
to ensure optimal cross-platform compatibility.
|
|
37
|
+
"""
|
|
38
|
+
logger.info(f"🔧 Applying platform fixes for {self.platform}")
|
|
39
|
+
|
|
40
|
+
if self.platform == "Windows":
|
|
41
|
+
self._apply_windows_fixes()
|
|
42
|
+
elif self.platform == "Darwin": # macOS
|
|
43
|
+
self._apply_macos_fixes()
|
|
44
|
+
elif self.platform == "Linux":
|
|
45
|
+
self._apply_linux_fixes()
|
|
46
|
+
|
|
47
|
+
logger.info(f"✅ Applied {len(self._applied_fixes)} platform fixes: {', '.join(self._applied_fixes)}")
|
|
48
|
+
|
|
49
|
+
def _apply_windows_fixes(self) -> None:
|
|
50
|
+
"""Apply Windows-specific fixes."""
|
|
51
|
+
|
|
52
|
+
# Fix 1: Set ProactorEventLoopPolicy for better asyncio performance
|
|
53
|
+
if self.python_version >= (3, 8):
|
|
54
|
+
try:
|
|
55
|
+
asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
|
|
56
|
+
self._applied_fixes.append("WindowsProactorEventLoopPolicy")
|
|
57
|
+
logger.debug("✅ Set WindowsProactorEventLoopPolicy for better asyncio performance")
|
|
58
|
+
except Exception as e:
|
|
59
|
+
logger.warning(f"⚠️ Failed to set WindowsProactorEventLoopPolicy: {e}")
|
|
60
|
+
|
|
61
|
+
# Fix 2: Suppress ResourceWarning on Windows
|
|
62
|
+
try:
|
|
63
|
+
warnings.filterwarnings("ignore", category=ResourceWarning)
|
|
64
|
+
self._applied_fixes.append("ResourceWarning suppression")
|
|
65
|
+
logger.debug("✅ Suppressed ResourceWarning for cleaner output")
|
|
66
|
+
except Exception as e:
|
|
67
|
+
logger.warning(f"⚠️ Failed to suppress ResourceWarning: {e}")
|
|
68
|
+
|
|
69
|
+
# Fix 3: Set console encoding to UTF-8 if possible
|
|
70
|
+
try:
|
|
71
|
+
if hasattr(sys.stdout, 'reconfigure'):
|
|
72
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
73
|
+
sys.stderr.reconfigure(encoding='utf-8')
|
|
74
|
+
self._applied_fixes.append("UTF-8 console encoding")
|
|
75
|
+
logger.debug("✅ Set console encoding to UTF-8")
|
|
76
|
+
except Exception as e:
|
|
77
|
+
logger.debug(f"Console encoding fix not needed or failed: {e}")
|
|
78
|
+
|
|
79
|
+
# Fix 4: Increase default socket timeout for Windows
|
|
80
|
+
try:
|
|
81
|
+
import socket
|
|
82
|
+
socket.setdefaulttimeout(30.0)
|
|
83
|
+
self._applied_fixes.append("Socket timeout increase")
|
|
84
|
+
logger.debug("✅ Increased default socket timeout to 30s")
|
|
85
|
+
except Exception as e:
|
|
86
|
+
logger.warning(f"⚠️ Failed to set socket timeout: {e}")
|
|
87
|
+
|
|
88
|
+
def _apply_macos_fixes(self) -> None:
|
|
89
|
+
"""Apply macOS-specific fixes."""
|
|
90
|
+
|
|
91
|
+
# Fix 1: Handle macOS SSL context issues
|
|
92
|
+
try:
|
|
93
|
+
import ssl
|
|
94
|
+
# Create unverified SSL context for development
|
|
95
|
+
ssl._create_default_https_context = ssl._create_unverified_context
|
|
96
|
+
self._applied_fixes.append("SSL context fix")
|
|
97
|
+
logger.debug("✅ Applied macOS SSL context fix")
|
|
98
|
+
except Exception as e:
|
|
99
|
+
logger.debug(f"SSL context fix not needed: {e}")
|
|
100
|
+
|
|
101
|
+
# Fix 2: Set optimal file descriptor limits
|
|
102
|
+
try:
|
|
103
|
+
import resource
|
|
104
|
+
soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
|
|
105
|
+
if soft < 1024:
|
|
106
|
+
resource.setrlimit(resource.RLIMIT_NOFILE, (min(1024, hard), hard))
|
|
107
|
+
self._applied_fixes.append("File descriptor limit increase")
|
|
108
|
+
logger.debug("✅ Increased file descriptor limit")
|
|
109
|
+
except Exception as e:
|
|
110
|
+
logger.debug(f"File descriptor limit fix not needed: {e}")
|
|
111
|
+
|
|
112
|
+
def _apply_linux_fixes(self) -> None:
|
|
113
|
+
"""Apply Linux-specific fixes."""
|
|
114
|
+
|
|
115
|
+
# Fix 1: Set optimal file descriptor limits
|
|
116
|
+
try:
|
|
117
|
+
import resource
|
|
118
|
+
soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
|
|
119
|
+
if soft < 2048:
|
|
120
|
+
resource.setrlimit(resource.RLIMIT_NOFILE, (min(2048, hard), hard))
|
|
121
|
+
self._applied_fixes.append("File descriptor limit increase")
|
|
122
|
+
logger.debug("✅ Increased file descriptor limit")
|
|
123
|
+
except Exception as e:
|
|
124
|
+
logger.debug(f"File descriptor limit fix not needed: {e}")
|
|
125
|
+
|
|
126
|
+
# Fix 2: Handle display issues for headless environments
|
|
127
|
+
try:
|
|
128
|
+
import os
|
|
129
|
+
if not os.environ.get('DISPLAY'):
|
|
130
|
+
os.environ['DISPLAY'] = ':99'
|
|
131
|
+
self._applied_fixes.append("Headless display fix")
|
|
132
|
+
logger.debug("✅ Set display for headless environment")
|
|
133
|
+
except Exception as e:
|
|
134
|
+
logger.debug(f"Display fix not needed: {e}")
|
|
135
|
+
|
|
136
|
+
def get_platform_info(self) -> dict:
|
|
137
|
+
"""
|
|
138
|
+
Get detailed platform information.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
Dictionary with platform details
|
|
142
|
+
"""
|
|
143
|
+
return {
|
|
144
|
+
'platform': self.platform,
|
|
145
|
+
'platform_release': platform.release(),
|
|
146
|
+
'platform_version': platform.version(),
|
|
147
|
+
'architecture': platform.architecture(),
|
|
148
|
+
'machine': platform.machine(),
|
|
149
|
+
'processor': platform.processor(),
|
|
150
|
+
'python_version': f"{self.python_version.major}.{self.python_version.minor}.{self.python_version.micro}",
|
|
151
|
+
'python_implementation': platform.python_implementation(),
|
|
152
|
+
'applied_fixes': self._applied_fixes
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
@classmethod
|
|
156
|
+
def auto_configure(cls) -> 'PlatformCompatibility':
|
|
157
|
+
"""
|
|
158
|
+
Automatically configure platform compatibility.
|
|
159
|
+
|
|
160
|
+
This is the recommended way to use this class - it will
|
|
161
|
+
automatically detect the platform and apply all necessary fixes.
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
Configured PlatformCompatibility instance
|
|
165
|
+
"""
|
|
166
|
+
compatibility = cls()
|
|
167
|
+
compatibility.apply_all_fixes()
|
|
168
|
+
return compatibility
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
# Global instance for easy access
|
|
172
|
+
_platform_compatibility: Optional[PlatformCompatibility] = None
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def ensure_platform_compatibility() -> PlatformCompatibility:
|
|
176
|
+
"""
|
|
177
|
+
Ensure platform compatibility is configured.
|
|
178
|
+
|
|
179
|
+
This function can be called multiple times safely - it will only
|
|
180
|
+
configure compatibility once per application run.
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
PlatformCompatibility instance
|
|
184
|
+
"""
|
|
185
|
+
global _platform_compatibility
|
|
186
|
+
|
|
187
|
+
if _platform_compatibility is None:
|
|
188
|
+
_platform_compatibility = PlatformCompatibility.auto_configure()
|
|
189
|
+
|
|
190
|
+
return _platform_compatibility
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def get_platform_info() -> dict:
|
|
194
|
+
"""
|
|
195
|
+
Get platform information.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
Dictionary with platform details
|
|
199
|
+
"""
|
|
200
|
+
compatibility = ensure_platform_compatibility()
|
|
201
|
+
return compatibility.get_platform_info()
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
# Auto-configure on import for convenience
|
|
205
|
+
ensure_platform_compatibility()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|