webscout 8.3.2__py3-none-any.whl → 8.3.4__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.
Potentially problematic release.
This version of webscout might be problematic. Click here for more details.
- webscout/AIutel.py +367 -41
- webscout/Bard.py +2 -22
- webscout/Bing_search.py +1 -2
- webscout/Provider/AISEARCH/__init__.py +1 -0
- webscout/Provider/AISEARCH/scira_search.py +24 -11
- webscout/Provider/AISEARCH/stellar_search.py +132 -0
- webscout/Provider/Deepinfra.py +75 -57
- webscout/Provider/ExaChat.py +93 -63
- webscout/Provider/Flowith.py +1 -1
- webscout/Provider/FreeGemini.py +2 -2
- webscout/Provider/Gemini.py +3 -10
- webscout/Provider/GeminiProxy.py +31 -5
- webscout/Provider/HeckAI.py +85 -80
- webscout/Provider/Jadve.py +56 -50
- webscout/Provider/LambdaChat.py +39 -31
- webscout/Provider/MiniMax.py +207 -0
- webscout/Provider/Nemotron.py +41 -13
- webscout/Provider/Netwrck.py +39 -59
- webscout/Provider/OLLAMA.py +8 -9
- webscout/Provider/OPENAI/BLACKBOXAI.py +0 -1
- webscout/Provider/OPENAI/MiniMax.py +298 -0
- webscout/Provider/OPENAI/README.md +31 -30
- webscout/Provider/OPENAI/TogetherAI.py +4 -17
- webscout/Provider/OPENAI/__init__.py +4 -2
- webscout/Provider/OPENAI/autoproxy.py +753 -18
- webscout/Provider/OPENAI/base.py +7 -76
- webscout/Provider/OPENAI/copilot.py +73 -26
- webscout/Provider/OPENAI/deepinfra.py +96 -132
- webscout/Provider/OPENAI/exachat.py +9 -5
- webscout/Provider/OPENAI/flowith.py +179 -166
- webscout/Provider/OPENAI/friendli.py +233 -0
- webscout/Provider/OPENAI/monochat.py +329 -0
- webscout/Provider/OPENAI/netwrck.py +4 -7
- webscout/Provider/OPENAI/pydantic_imports.py +1 -172
- webscout/Provider/OPENAI/qodo.py +630 -0
- webscout/Provider/OPENAI/scirachat.py +82 -49
- webscout/Provider/OPENAI/textpollinations.py +13 -12
- webscout/Provider/OPENAI/toolbaz.py +1 -0
- webscout/Provider/OPENAI/typegpt.py +4 -4
- webscout/Provider/OPENAI/utils.py +19 -42
- webscout/Provider/OPENAI/x0gpt.py +14 -2
- webscout/Provider/OpenGPT.py +54 -32
- webscout/Provider/PI.py +58 -84
- webscout/Provider/Qodo.py +454 -0
- webscout/Provider/StandardInput.py +32 -13
- webscout/Provider/TTI/README.md +9 -9
- webscout/Provider/TTI/__init__.py +2 -1
- webscout/Provider/TTI/aiarta.py +92 -78
- webscout/Provider/TTI/infip.py +212 -0
- webscout/Provider/TTI/monochat.py +220 -0
- webscout/Provider/TeachAnything.py +11 -3
- webscout/Provider/TextPollinationsAI.py +91 -82
- webscout/Provider/TogetherAI.py +32 -48
- webscout/Provider/Venice.py +37 -46
- webscout/Provider/VercelAI.py +27 -24
- webscout/Provider/WiseCat.py +35 -35
- webscout/Provider/WrDoChat.py +22 -26
- webscout/Provider/WritingMate.py +26 -22
- webscout/Provider/__init__.py +6 -6
- webscout/Provider/copilot.py +58 -61
- webscout/Provider/freeaichat.py +64 -55
- webscout/Provider/granite.py +48 -57
- webscout/Provider/koala.py +51 -39
- webscout/Provider/learnfastai.py +49 -64
- webscout/Provider/llmchat.py +79 -93
- webscout/Provider/llmchatco.py +63 -78
- webscout/Provider/monochat.py +275 -0
- webscout/Provider/multichat.py +51 -40
- webscout/Provider/oivscode.py +1 -1
- webscout/Provider/scira_chat.py +257 -104
- webscout/Provider/scnet.py +13 -13
- webscout/Provider/searchchat.py +13 -13
- webscout/Provider/sonus.py +12 -11
- webscout/Provider/toolbaz.py +25 -8
- webscout/Provider/turboseek.py +41 -42
- webscout/Provider/typefully.py +27 -12
- webscout/Provider/typegpt.py +43 -48
- webscout/Provider/uncovr.py +55 -90
- webscout/Provider/x0gpt.py +325 -299
- webscout/Provider/yep.py +79 -96
- webscout/__init__.py +7 -2
- webscout/auth/__init__.py +12 -1
- webscout/auth/providers.py +27 -5
- webscout/auth/routes.py +146 -105
- webscout/auth/server.py +367 -312
- webscout/client.py +121 -116
- webscout/litagent/Readme.md +68 -55
- webscout/litagent/agent.py +99 -9
- webscout/version.py +1 -1
- {webscout-8.3.2.dist-info → webscout-8.3.4.dist-info}/METADATA +102 -91
- {webscout-8.3.2.dist-info → webscout-8.3.4.dist-info}/RECORD +95 -107
- webscout/Provider/AI21.py +0 -177
- webscout/Provider/HuggingFaceChat.py +0 -469
- webscout/Provider/OPENAI/freeaichat.py +0 -363
- webscout/Provider/TTI/fastflux.py +0 -233
- webscout/Provider/Writecream.py +0 -246
- webscout/auth/static/favicon.svg +0 -11
- webscout/auth/swagger_ui.py +0 -203
- webscout/auth/templates/components/authentication.html +0 -237
- webscout/auth/templates/components/base.html +0 -103
- webscout/auth/templates/components/endpoints.html +0 -750
- webscout/auth/templates/components/examples.html +0 -491
- webscout/auth/templates/components/footer.html +0 -75
- webscout/auth/templates/components/header.html +0 -27
- webscout/auth/templates/components/models.html +0 -286
- webscout/auth/templates/components/navigation.html +0 -70
- webscout/auth/templates/static/api.js +0 -455
- webscout/auth/templates/static/icons.js +0 -168
- webscout/auth/templates/static/main.js +0 -784
- webscout/auth/templates/static/particles.js +0 -201
- webscout/auth/templates/static/styles.css +0 -3353
- webscout/auth/templates/static/ui.js +0 -374
- webscout/auth/templates/swagger_ui.html +0 -170
- {webscout-8.3.2.dist-info → webscout-8.3.4.dist-info}/WHEEL +0 -0
- {webscout-8.3.2.dist-info → webscout-8.3.4.dist-info}/entry_points.txt +0 -0
- {webscout-8.3.2.dist-info → webscout-8.3.4.dist-info}/licenses/LICENSE.md +0 -0
- {webscout-8.3.2.dist-info → webscout-8.3.4.dist-info}/top_level.txt +0 -0
|
@@ -6,8 +6,11 @@ This module provides automatic proxy injection for HTTP sessions using a remote
|
|
|
6
6
|
import random
|
|
7
7
|
import time
|
|
8
8
|
from abc import ABCMeta
|
|
9
|
-
from typing import Dict, List, Optional, Any
|
|
9
|
+
from typing import Dict, List, Optional, Any, Callable, Union
|
|
10
10
|
import requests
|
|
11
|
+
import functools
|
|
12
|
+
from contextlib import contextmanager
|
|
13
|
+
import types
|
|
11
14
|
|
|
12
15
|
# Optional imports for different HTTP clients
|
|
13
16
|
try:
|
|
@@ -29,7 +32,78 @@ _proxy_cache = {
|
|
|
29
32
|
'cache_duration': 300 # 5 minutes
|
|
30
33
|
}
|
|
31
34
|
|
|
32
|
-
PROXY_SOURCE_URL = "
|
|
35
|
+
PROXY_SOURCE_URL = "https://proxies.typegpt.net/ips.txt"
|
|
36
|
+
|
|
37
|
+
# --- Static Proxy Lists ---
|
|
38
|
+
# NordVPN proxies (format: https://host:port:user:pass)
|
|
39
|
+
STATIC_NORDVPN_PROXIES = [
|
|
40
|
+
"https://pl128.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
41
|
+
"https://be148.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
42
|
+
"https://hu48.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
43
|
+
"https://us5063.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
44
|
+
"https://at86.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
45
|
+
"https://ch217.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
46
|
+
"https://dk152.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
47
|
+
"https://no151.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
48
|
+
"https://ch218.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
49
|
+
"https://uk1784.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
50
|
+
"https://fr555.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
51
|
+
"https://ch219.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
52
|
+
"https://us5064.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
53
|
+
"https://uk765.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
54
|
+
"https://uk812.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
55
|
+
"https://uk813.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
56
|
+
"https://uk814.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
57
|
+
"https://uk871.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
58
|
+
"https://uk873.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
59
|
+
"https://uk875.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
60
|
+
"https://uk877.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
61
|
+
"https://uk879.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
62
|
+
"https://uk884.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
63
|
+
"https://uk886.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
64
|
+
"https://be149.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
65
|
+
"https://uk1806.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
66
|
+
"https://uk888.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
67
|
+
"https://uk890.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
68
|
+
"https://uk892.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
69
|
+
"https://uk894.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
70
|
+
"https://uk896.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
71
|
+
"https://uk898.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
72
|
+
"https://us5055.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
73
|
+
"https://jp429.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
74
|
+
"https://it132.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
75
|
+
"https://us4735.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
76
|
+
"https://pl122.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
77
|
+
"https://cz93.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
78
|
+
"https://at80.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
79
|
+
"https://ro59.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
80
|
+
"https://ch198.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
81
|
+
"https://bg38.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
82
|
+
"https://hu47.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
83
|
+
"https://jp454.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
84
|
+
"https://dk150.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
85
|
+
"https://de750.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
86
|
+
"https://pl125.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
87
|
+
"https://us5057.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
88
|
+
"https://us5058.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
89
|
+
"https://us5059.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
90
|
+
"https://us5060.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJVZdy8KKcEW3ZE5",
|
|
91
|
+
"https://no141.nordvpn.com:89:WZBVNB9MCuZu3FLX3D1rUc8a:XRBU8tofEJ"
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
# Webshare rotating proxies (format: http://user:pass@host:port)
|
|
95
|
+
STATIC_WEBSHARE_PROXIES = [
|
|
96
|
+
"http://kkuafwyh-rotate:kl6esmu21js3@p.webshare.io:80",
|
|
97
|
+
"http://stzaxffz-rotate:ax92ravj1pmm@p.webshare.io:80",
|
|
98
|
+
"http://nfokjhhu-rotate:ez248bgee4z9@p.webshare.io:80",
|
|
99
|
+
"http://fiupzkjx-rotate:0zlrd2in3mrh@p.webshare.io:80",
|
|
100
|
+
"http://xukpnkpr-rotate:hcmwl8cl4iyw@p.webshare.io:80",
|
|
101
|
+
"http://tndgqbid-rotate:qb1cgkl4irh4@p.webshare.io:80",
|
|
102
|
+
"http://nnpnjrmj-rotate:8bj089tzcwhz@p.webshare.io:80",
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
# Combine all static proxies
|
|
106
|
+
STATIC_PROXIES = STATIC_NORDVPN_PROXIES + STATIC_WEBSHARE_PROXIES
|
|
33
107
|
|
|
34
108
|
|
|
35
109
|
def fetch_proxies() -> List[str]:
|
|
@@ -75,22 +149,27 @@ def get_cached_proxies() -> List[str]:
|
|
|
75
149
|
else:
|
|
76
150
|
pass
|
|
77
151
|
|
|
78
|
-
|
|
152
|
+
# Priority: Webshare -> remote -> NordVPN
|
|
153
|
+
proxies = STATIC_WEBSHARE_PROXIES + _proxy_cache['proxies'] + STATIC_NORDVPN_PROXIES
|
|
154
|
+
proxies = list(dict.fromkeys(proxies)) # Remove duplicates, preserve order
|
|
155
|
+
return proxies
|
|
79
156
|
|
|
80
157
|
|
|
81
158
|
def get_auto_proxy() -> Optional[str]:
|
|
82
159
|
"""
|
|
83
|
-
Get a random proxy
|
|
160
|
+
Get a random proxy, prioritizing Webshare proxies if available.
|
|
84
161
|
|
|
85
162
|
Returns:
|
|
86
163
|
Optional[str]: A proxy URL or None if no proxies available
|
|
87
164
|
"""
|
|
88
165
|
proxies = get_cached_proxies()
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
166
|
+
# Try Webshare proxies first
|
|
167
|
+
webshare = [p for p in proxies if p in STATIC_WEBSHARE_PROXIES]
|
|
168
|
+
if webshare:
|
|
169
|
+
return random.choice(webshare)
|
|
170
|
+
if proxies:
|
|
171
|
+
return random.choice(proxies)
|
|
172
|
+
return None
|
|
94
173
|
|
|
95
174
|
|
|
96
175
|
def get_proxy_dict(proxy_url: Optional[str] = None) -> Dict[str, str]:
|
|
@@ -168,10 +247,13 @@ class ProxyAutoMeta(ABCMeta):
|
|
|
168
247
|
proxies = {}
|
|
169
248
|
|
|
170
249
|
instance.proxies = proxies
|
|
250
|
+
|
|
251
|
+
# Set default max proxy attempts for auto-retry functionality
|
|
252
|
+
if not hasattr(instance, '_max_proxy_attempts'):
|
|
253
|
+
instance._max_proxy_attempts = kwargs.get('max_proxy_attempts', 2)
|
|
171
254
|
|
|
172
|
-
#
|
|
173
|
-
|
|
174
|
-
_patch_instance_sessions(instance, proxies)
|
|
255
|
+
# Always patch existing sessions (for both proxy and auto-retry functionality)
|
|
256
|
+
_patch_instance_sessions(instance, proxies)
|
|
175
257
|
|
|
176
258
|
# Provide helper methods for creating proxied sessions
|
|
177
259
|
_add_proxy_helpers(instance, proxies)
|
|
@@ -181,7 +263,7 @@ class ProxyAutoMeta(ABCMeta):
|
|
|
181
263
|
|
|
182
264
|
def _patch_instance_sessions(instance: Any, proxies: Dict[str, str]) -> None:
|
|
183
265
|
"""
|
|
184
|
-
Patch existing session objects on the instance with proxy configuration.
|
|
266
|
+
Patch existing session objects on the instance with proxy configuration and auto-retry functionality.
|
|
185
267
|
|
|
186
268
|
Args:
|
|
187
269
|
instance: The class instance to patch
|
|
@@ -196,27 +278,35 @@ def _patch_instance_sessions(instance: Any, proxies: Dict[str, str]) -> None:
|
|
|
196
278
|
|
|
197
279
|
# Patch requests.Session objects
|
|
198
280
|
if isinstance(attr_obj, requests.Session):
|
|
199
|
-
|
|
281
|
+
if proxies:
|
|
282
|
+
attr_obj.proxies.update(proxies)
|
|
283
|
+
_add_auto_retry_to_session(attr_obj, instance)
|
|
200
284
|
|
|
201
285
|
# Patch httpx.Client objects
|
|
202
286
|
elif httpx and isinstance(attr_obj, httpx.Client):
|
|
203
287
|
try:
|
|
204
288
|
# httpx uses different proxy format
|
|
205
|
-
|
|
289
|
+
if proxies:
|
|
290
|
+
attr_obj._proxies = proxies
|
|
291
|
+
_add_auto_retry_to_httpx_client(attr_obj, instance)
|
|
206
292
|
except Exception:
|
|
207
293
|
pass
|
|
208
294
|
|
|
209
295
|
# Patch curl_cffi Session objects
|
|
210
296
|
elif CurlSession and isinstance(attr_obj, CurlSession):
|
|
211
297
|
try:
|
|
212
|
-
|
|
298
|
+
if proxies:
|
|
299
|
+
attr_obj.proxies.update(proxies)
|
|
300
|
+
_add_auto_retry_to_curl_session(attr_obj, instance)
|
|
213
301
|
except Exception:
|
|
214
302
|
pass
|
|
215
303
|
|
|
216
304
|
# Patch curl_cffi AsyncSession objects
|
|
217
305
|
elif CurlAsyncSession and isinstance(attr_obj, CurlAsyncSession):
|
|
218
306
|
try:
|
|
219
|
-
|
|
307
|
+
if proxies:
|
|
308
|
+
attr_obj.proxies.update(proxies)
|
|
309
|
+
_add_auto_retry_to_curl_async_session(attr_obj, instance)
|
|
220
310
|
except Exception:
|
|
221
311
|
pass
|
|
222
312
|
|
|
@@ -224,6 +314,265 @@ def _patch_instance_sessions(instance: Any, proxies: Dict[str, str]) -> None:
|
|
|
224
314
|
continue
|
|
225
315
|
|
|
226
316
|
|
|
317
|
+
def _add_auto_retry_to_session(session: requests.Session, instance: Any) -> None:
|
|
318
|
+
"""
|
|
319
|
+
Add auto-retry functionality to a requests.Session object.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
session: The requests.Session to patch
|
|
323
|
+
instance: The provider instance for context
|
|
324
|
+
"""
|
|
325
|
+
if hasattr(session, '_auto_retry_patched'):
|
|
326
|
+
return # Already patched
|
|
327
|
+
|
|
328
|
+
original_request = session.request
|
|
329
|
+
|
|
330
|
+
def request_with_auto_retry(method, url, **kwargs):
|
|
331
|
+
max_proxy_attempts = getattr(instance, '_max_proxy_attempts', 2)
|
|
332
|
+
original_proxies = session.proxies.copy()
|
|
333
|
+
first_error = None
|
|
334
|
+
|
|
335
|
+
# First attempt with current proxy configuration
|
|
336
|
+
try:
|
|
337
|
+
return original_request(method, url, **kwargs)
|
|
338
|
+
except Exception as e:
|
|
339
|
+
first_error = e
|
|
340
|
+
|
|
341
|
+
# If we have proxies configured, try different ones
|
|
342
|
+
if original_proxies:
|
|
343
|
+
proxy_attempts = 0
|
|
344
|
+
|
|
345
|
+
while proxy_attempts < max_proxy_attempts:
|
|
346
|
+
try:
|
|
347
|
+
# Get a new proxy
|
|
348
|
+
new_proxy_url = get_auto_proxy()
|
|
349
|
+
if new_proxy_url:
|
|
350
|
+
new_proxies = get_proxy_dict(new_proxy_url)
|
|
351
|
+
session.proxies.clear()
|
|
352
|
+
session.proxies.update(new_proxies)
|
|
353
|
+
|
|
354
|
+
# Try the request with new proxy
|
|
355
|
+
return original_request(method, url, **kwargs)
|
|
356
|
+
else:
|
|
357
|
+
break # No more proxies available
|
|
358
|
+
|
|
359
|
+
except Exception:
|
|
360
|
+
proxy_attempts += 1
|
|
361
|
+
continue
|
|
362
|
+
|
|
363
|
+
# All proxy attempts failed, try without proxy
|
|
364
|
+
try:
|
|
365
|
+
session.proxies.clear()
|
|
366
|
+
return original_request(method, url, **kwargs)
|
|
367
|
+
except Exception:
|
|
368
|
+
# Restore original proxy settings and re-raise the first error
|
|
369
|
+
session.proxies.clear()
|
|
370
|
+
session.proxies.update(original_proxies)
|
|
371
|
+
raise first_error
|
|
372
|
+
else:
|
|
373
|
+
# No proxies were configured, just re-raise the original error
|
|
374
|
+
raise first_error
|
|
375
|
+
|
|
376
|
+
session.request = request_with_auto_retry
|
|
377
|
+
session._auto_retry_patched = True
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def _add_auto_retry_to_httpx_client(client, instance: Any) -> None:
|
|
381
|
+
"""
|
|
382
|
+
Add auto-retry functionality to an httpx.Client object.
|
|
383
|
+
|
|
384
|
+
Args:
|
|
385
|
+
client: The httpx.Client to patch
|
|
386
|
+
instance: The provider instance for context
|
|
387
|
+
"""
|
|
388
|
+
if not httpx or hasattr(client, '_auto_retry_patched'):
|
|
389
|
+
return # Not available or already patched
|
|
390
|
+
|
|
391
|
+
try:
|
|
392
|
+
original_request = client.request
|
|
393
|
+
|
|
394
|
+
def request_with_auto_retry(method, url, **kwargs):
|
|
395
|
+
max_proxy_attempts = getattr(instance, '_max_proxy_attempts', 2)
|
|
396
|
+
original_proxies = getattr(client, '_proxies', {}).copy()
|
|
397
|
+
first_error = None
|
|
398
|
+
|
|
399
|
+
# First attempt with current proxy configuration
|
|
400
|
+
try:
|
|
401
|
+
return original_request(method, url, **kwargs)
|
|
402
|
+
except Exception as e:
|
|
403
|
+
first_error = e
|
|
404
|
+
|
|
405
|
+
# If we have proxies configured, try different ones
|
|
406
|
+
if original_proxies:
|
|
407
|
+
proxy_attempts = 0
|
|
408
|
+
|
|
409
|
+
while proxy_attempts < max_proxy_attempts:
|
|
410
|
+
try:
|
|
411
|
+
# Get a new proxy
|
|
412
|
+
new_proxy_url = get_auto_proxy()
|
|
413
|
+
if new_proxy_url:
|
|
414
|
+
new_proxies = get_proxy_dict(new_proxy_url)
|
|
415
|
+
client._proxies = new_proxies
|
|
416
|
+
|
|
417
|
+
# Try the request with new proxy
|
|
418
|
+
return original_request(method, url, **kwargs)
|
|
419
|
+
else:
|
|
420
|
+
break # No more proxies available
|
|
421
|
+
|
|
422
|
+
except Exception:
|
|
423
|
+
proxy_attempts += 1
|
|
424
|
+
continue
|
|
425
|
+
|
|
426
|
+
# All proxy attempts failed, try without proxy
|
|
427
|
+
try:
|
|
428
|
+
client._proxies = {}
|
|
429
|
+
return original_request(method, url, **kwargs)
|
|
430
|
+
except Exception:
|
|
431
|
+
# Restore original proxy settings and re-raise the first error
|
|
432
|
+
client._proxies = original_proxies
|
|
433
|
+
raise first_error
|
|
434
|
+
else:
|
|
435
|
+
# No proxies were configured, just re-raise the original error
|
|
436
|
+
raise first_error
|
|
437
|
+
|
|
438
|
+
client.request = request_with_auto_retry
|
|
439
|
+
client._auto_retry_patched = True
|
|
440
|
+
except Exception:
|
|
441
|
+
pass
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def _add_auto_retry_to_curl_session(session, instance: Any) -> None:
|
|
445
|
+
"""
|
|
446
|
+
Add auto-retry functionality to a curl_cffi.Session object.
|
|
447
|
+
|
|
448
|
+
Args:
|
|
449
|
+
session: The curl_cffi.Session to patch
|
|
450
|
+
instance: The provider instance for context
|
|
451
|
+
"""
|
|
452
|
+
if not CurlSession or hasattr(session, '_auto_retry_patched'):
|
|
453
|
+
return # Not available or already patched
|
|
454
|
+
|
|
455
|
+
try:
|
|
456
|
+
original_request = session.request
|
|
457
|
+
|
|
458
|
+
def request_with_auto_retry(method, url, **kwargs):
|
|
459
|
+
max_proxy_attempts = getattr(instance, '_max_proxy_attempts', 2)
|
|
460
|
+
original_proxies = session.proxies.copy()
|
|
461
|
+
first_error = None
|
|
462
|
+
|
|
463
|
+
# First attempt with current proxy configuration
|
|
464
|
+
try:
|
|
465
|
+
return original_request(method, url, **kwargs)
|
|
466
|
+
except Exception as e:
|
|
467
|
+
first_error = e
|
|
468
|
+
|
|
469
|
+
# If we have proxies configured, try different ones
|
|
470
|
+
if original_proxies:
|
|
471
|
+
proxy_attempts = 0
|
|
472
|
+
|
|
473
|
+
while proxy_attempts < max_proxy_attempts:
|
|
474
|
+
try:
|
|
475
|
+
# Get a new proxy
|
|
476
|
+
new_proxy_url = get_auto_proxy()
|
|
477
|
+
if new_proxy_url:
|
|
478
|
+
new_proxies = get_proxy_dict(new_proxy_url)
|
|
479
|
+
session.proxies.clear()
|
|
480
|
+
session.proxies.update(new_proxies)
|
|
481
|
+
|
|
482
|
+
# Try the request with new proxy
|
|
483
|
+
return original_request(method, url, **kwargs)
|
|
484
|
+
else:
|
|
485
|
+
break # No more proxies available
|
|
486
|
+
|
|
487
|
+
except Exception:
|
|
488
|
+
proxy_attempts += 1
|
|
489
|
+
continue
|
|
490
|
+
|
|
491
|
+
# All proxy attempts failed, try without proxy
|
|
492
|
+
try:
|
|
493
|
+
session.proxies.clear()
|
|
494
|
+
return original_request(method, url, **kwargs)
|
|
495
|
+
except Exception:
|
|
496
|
+
# Restore original proxy settings and re-raise the first error
|
|
497
|
+
session.proxies.clear()
|
|
498
|
+
session.proxies.update(original_proxies)
|
|
499
|
+
raise first_error
|
|
500
|
+
else:
|
|
501
|
+
# No proxies were configured, just re-raise the original error
|
|
502
|
+
raise first_error
|
|
503
|
+
|
|
504
|
+
session.request = request_with_auto_retry
|
|
505
|
+
session._auto_retry_patched = True
|
|
506
|
+
except Exception:
|
|
507
|
+
pass
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def _add_auto_retry_to_curl_async_session(session, instance: Any) -> None:
|
|
511
|
+
"""
|
|
512
|
+
Add auto-retry functionality to a curl_cffi.AsyncSession object.
|
|
513
|
+
|
|
514
|
+
Args:
|
|
515
|
+
session: The curl_cffi.AsyncSession to patch
|
|
516
|
+
instance: The provider instance for context
|
|
517
|
+
"""
|
|
518
|
+
if not CurlAsyncSession or hasattr(session, '_auto_retry_patched'):
|
|
519
|
+
return # Not available or already patched
|
|
520
|
+
|
|
521
|
+
try:
|
|
522
|
+
original_request = session.request
|
|
523
|
+
|
|
524
|
+
async def request_with_auto_retry(method, url, **kwargs):
|
|
525
|
+
max_proxy_attempts = getattr(instance, '_max_proxy_attempts', 2)
|
|
526
|
+
original_proxies = session.proxies.copy()
|
|
527
|
+
first_error = None
|
|
528
|
+
|
|
529
|
+
# First attempt with current proxy configuration
|
|
530
|
+
try:
|
|
531
|
+
return await original_request(method, url, **kwargs)
|
|
532
|
+
except Exception as e:
|
|
533
|
+
first_error = e
|
|
534
|
+
|
|
535
|
+
# If we have proxies configured, try different ones
|
|
536
|
+
if original_proxies:
|
|
537
|
+
proxy_attempts = 0
|
|
538
|
+
|
|
539
|
+
while proxy_attempts < max_proxy_attempts:
|
|
540
|
+
try:
|
|
541
|
+
# Get a new proxy
|
|
542
|
+
new_proxy_url = get_auto_proxy()
|
|
543
|
+
if new_proxy_url:
|
|
544
|
+
new_proxies = get_proxy_dict(new_proxy_url)
|
|
545
|
+
session.proxies.clear()
|
|
546
|
+
session.proxies.update(new_proxies)
|
|
547
|
+
|
|
548
|
+
# Try the request with new proxy
|
|
549
|
+
return await original_request(method, url, **kwargs)
|
|
550
|
+
else:
|
|
551
|
+
break # No more proxies available
|
|
552
|
+
|
|
553
|
+
except Exception:
|
|
554
|
+
proxy_attempts += 1
|
|
555
|
+
continue
|
|
556
|
+
|
|
557
|
+
# All proxy attempts failed, try without proxy
|
|
558
|
+
try:
|
|
559
|
+
session.proxies.clear()
|
|
560
|
+
return await original_request(method, url, **kwargs)
|
|
561
|
+
except Exception:
|
|
562
|
+
# Restore original proxy settings and re-raise the first error
|
|
563
|
+
session.proxies.clear()
|
|
564
|
+
session.proxies.update(original_proxies)
|
|
565
|
+
raise first_error
|
|
566
|
+
else:
|
|
567
|
+
# No proxies were configured, just re-raise the original error
|
|
568
|
+
raise first_error
|
|
569
|
+
|
|
570
|
+
session.request = request_with_auto_retry
|
|
571
|
+
session._auto_retry_patched = True
|
|
572
|
+
except Exception:
|
|
573
|
+
pass
|
|
574
|
+
|
|
575
|
+
|
|
227
576
|
def _add_proxy_helpers(instance: Any, proxies: Dict[str, str]) -> None:
|
|
228
577
|
"""
|
|
229
578
|
Add helper methods to the instance for creating proxied sessions.
|
|
@@ -260,11 +609,39 @@ def _add_proxy_helpers(instance: Any, proxies: Dict[str, str]) -> None:
|
|
|
260
609
|
else:
|
|
261
610
|
raise ImportError("curl_cffi is not installed")
|
|
262
611
|
|
|
612
|
+
def get_auto_retry_session(max_proxy_attempts: int = 2):
|
|
613
|
+
"""Get a requests.Session with automatic proxy retry and fallback functionality"""
|
|
614
|
+
return create_auto_retry_session(max_proxy_attempts)
|
|
615
|
+
|
|
616
|
+
def make_auto_retry_request(method: str, url: str, max_proxy_attempts: int = 2, **kwargs):
|
|
617
|
+
"""Make a request with automatic proxy retry and fallback"""
|
|
618
|
+
return make_request_with_auto_retry(
|
|
619
|
+
method=method,
|
|
620
|
+
url=url,
|
|
621
|
+
max_proxy_attempts=max_proxy_attempts,
|
|
622
|
+
**kwargs
|
|
623
|
+
)
|
|
624
|
+
|
|
625
|
+
def patch_session_with_auto_retry(session_obj):
|
|
626
|
+
"""Patch any session object with auto-retry functionality"""
|
|
627
|
+
if isinstance(session_obj, requests.Session):
|
|
628
|
+
_add_auto_retry_to_session(session_obj, instance)
|
|
629
|
+
elif httpx and isinstance(session_obj, httpx.Client):
|
|
630
|
+
_add_auto_retry_to_httpx_client(session_obj, instance)
|
|
631
|
+
elif CurlSession and isinstance(session_obj, CurlSession):
|
|
632
|
+
_add_auto_retry_to_curl_session(session_obj, instance)
|
|
633
|
+
elif CurlAsyncSession and isinstance(session_obj, CurlAsyncSession):
|
|
634
|
+
_add_auto_retry_to_curl_async_session(session_obj, instance)
|
|
635
|
+
return session_obj
|
|
636
|
+
|
|
263
637
|
# Add methods to instance
|
|
264
638
|
instance.get_proxied_session = get_proxied_session
|
|
265
639
|
instance.get_proxied_httpx_client = get_proxied_httpx_client
|
|
266
640
|
instance.get_proxied_curl_session = get_proxied_curl_session
|
|
267
641
|
instance.get_proxied_curl_async_session = get_proxied_curl_async_session
|
|
642
|
+
instance.get_auto_retry_session = get_auto_retry_session
|
|
643
|
+
instance.make_auto_retry_request = make_auto_retry_request
|
|
644
|
+
instance.patch_session_with_auto_retry = patch_session_with_auto_retry
|
|
268
645
|
|
|
269
646
|
|
|
270
647
|
def get_working_proxy(max_attempts: int = 5, timeout: int = 10) -> Optional[str]:
|
|
@@ -329,4 +706,362 @@ def set_proxy_cache_duration(duration: int) -> None:
|
|
|
329
706
|
duration: Cache duration in seconds
|
|
330
707
|
"""
|
|
331
708
|
global _proxy_cache
|
|
332
|
-
_proxy_cache['cache_duration'] = duration
|
|
709
|
+
_proxy_cache['cache_duration'] = duration
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
def auto_retry_with_fallback(max_proxy_attempts: int = 2, timeout: int = 10):
|
|
713
|
+
"""
|
|
714
|
+
Decorator that automatically retries requests with different proxies and falls back to no proxy.
|
|
715
|
+
|
|
716
|
+
This decorator will:
|
|
717
|
+
1. Try the request with the current proxy
|
|
718
|
+
2. If it fails, try with up to max_proxy_attempts different proxies
|
|
719
|
+
3. If all proxies fail, retry without any proxy
|
|
720
|
+
|
|
721
|
+
Args:
|
|
722
|
+
max_proxy_attempts: Maximum number of proxy attempts before falling back to no proxy
|
|
723
|
+
timeout: Timeout for each request attempt
|
|
724
|
+
|
|
725
|
+
Returns:
|
|
726
|
+
Decorator function
|
|
727
|
+
"""
|
|
728
|
+
def decorator(func: Callable) -> Callable:
|
|
729
|
+
@functools.wraps(func)
|
|
730
|
+
def wrapper(*args, **kwargs):
|
|
731
|
+
# Track the original instance and its proxy settings
|
|
732
|
+
instance = args[0] if args else None
|
|
733
|
+
original_proxies = getattr(instance, 'proxies', {}) if instance else {}
|
|
734
|
+
|
|
735
|
+
# First attempt with current proxy configuration
|
|
736
|
+
try:
|
|
737
|
+
return func(*args, **kwargs)
|
|
738
|
+
except Exception as e:
|
|
739
|
+
first_error = e
|
|
740
|
+
|
|
741
|
+
# If we have proxies configured, try different ones
|
|
742
|
+
if original_proxies and instance:
|
|
743
|
+
proxy_attempts = 0
|
|
744
|
+
|
|
745
|
+
while proxy_attempts < max_proxy_attempts:
|
|
746
|
+
try:
|
|
747
|
+
# Get a new proxy
|
|
748
|
+
new_proxy_url = get_auto_proxy()
|
|
749
|
+
if new_proxy_url:
|
|
750
|
+
new_proxies = get_proxy_dict(new_proxy_url)
|
|
751
|
+
instance.proxies = new_proxies
|
|
752
|
+
|
|
753
|
+
# Update any existing sessions with new proxy
|
|
754
|
+
_patch_instance_sessions(instance, new_proxies)
|
|
755
|
+
|
|
756
|
+
# Try the request with new proxy
|
|
757
|
+
return func(*args, **kwargs)
|
|
758
|
+
else:
|
|
759
|
+
break # No more proxies available
|
|
760
|
+
|
|
761
|
+
except Exception:
|
|
762
|
+
proxy_attempts += 1
|
|
763
|
+
continue
|
|
764
|
+
|
|
765
|
+
# All proxy attempts failed, try without proxy
|
|
766
|
+
try:
|
|
767
|
+
instance.proxies = {}
|
|
768
|
+
_patch_instance_sessions(instance, {})
|
|
769
|
+
return func(*args, **kwargs)
|
|
770
|
+
except Exception:
|
|
771
|
+
# Restore original proxy settings and re-raise the first error
|
|
772
|
+
instance.proxies = original_proxies
|
|
773
|
+
_patch_instance_sessions(instance, original_proxies)
|
|
774
|
+
raise first_error
|
|
775
|
+
else:
|
|
776
|
+
# No proxies were configured, just re-raise the original error
|
|
777
|
+
raise first_error
|
|
778
|
+
|
|
779
|
+
return wrapper
|
|
780
|
+
return decorator
|
|
781
|
+
|
|
782
|
+
|
|
783
|
+
def make_request_with_auto_retry(
|
|
784
|
+
method: str,
|
|
785
|
+
url: str,
|
|
786
|
+
session: Optional[Union[requests.Session, Any]] = None,
|
|
787
|
+
max_proxy_attempts: int = 2,
|
|
788
|
+
timeout: int = 10,
|
|
789
|
+
**kwargs
|
|
790
|
+
) -> requests.Response:
|
|
791
|
+
"""
|
|
792
|
+
Make an HTTP request with automatic proxy retry and fallback.
|
|
793
|
+
|
|
794
|
+
This function will:
|
|
795
|
+
1. Try the request with the current session configuration
|
|
796
|
+
2. If it fails and proxies are configured, try with different proxies
|
|
797
|
+
3. If all proxies fail, retry without any proxy
|
|
798
|
+
|
|
799
|
+
Args:
|
|
800
|
+
method: HTTP method (GET, POST, etc.)
|
|
801
|
+
url: Request URL
|
|
802
|
+
session: Optional session object to use
|
|
803
|
+
max_proxy_attempts: Maximum number of proxy attempts before falling back
|
|
804
|
+
timeout: Request timeout
|
|
805
|
+
**kwargs: Additional arguments to pass to the request
|
|
806
|
+
|
|
807
|
+
Returns:
|
|
808
|
+
requests.Response: The successful response
|
|
809
|
+
|
|
810
|
+
Raises:
|
|
811
|
+
Exception: If all attempts fail
|
|
812
|
+
"""
|
|
813
|
+
if session is None:
|
|
814
|
+
session = requests.Session()
|
|
815
|
+
|
|
816
|
+
original_proxies = getattr(session, 'proxies', {}).copy()
|
|
817
|
+
first_error = None
|
|
818
|
+
|
|
819
|
+
# First attempt with current configuration
|
|
820
|
+
try:
|
|
821
|
+
return session.request(method, url, timeout=timeout, **kwargs)
|
|
822
|
+
except Exception as e:
|
|
823
|
+
first_error = e
|
|
824
|
+
|
|
825
|
+
# If we have proxies configured, try different ones
|
|
826
|
+
if original_proxies:
|
|
827
|
+
proxy_attempts = 0
|
|
828
|
+
|
|
829
|
+
while proxy_attempts < max_proxy_attempts:
|
|
830
|
+
try:
|
|
831
|
+
# Get a new proxy
|
|
832
|
+
new_proxy_url = get_auto_proxy()
|
|
833
|
+
if new_proxy_url:
|
|
834
|
+
new_proxies = get_proxy_dict(new_proxy_url)
|
|
835
|
+
session.proxies.clear()
|
|
836
|
+
session.proxies.update(new_proxies)
|
|
837
|
+
|
|
838
|
+
# Try the request with new proxy
|
|
839
|
+
return session.request(method, url, timeout=timeout, **kwargs)
|
|
840
|
+
else:
|
|
841
|
+
break # No more proxies available
|
|
842
|
+
|
|
843
|
+
except Exception:
|
|
844
|
+
proxy_attempts += 1
|
|
845
|
+
continue
|
|
846
|
+
|
|
847
|
+
# All proxy attempts failed, try without proxy
|
|
848
|
+
try:
|
|
849
|
+
session.proxies.clear()
|
|
850
|
+
return session.request(method, url, timeout=timeout, **kwargs)
|
|
851
|
+
except Exception:
|
|
852
|
+
# Restore original proxy settings and re-raise the first error
|
|
853
|
+
session.proxies.clear()
|
|
854
|
+
session.proxies.update(original_proxies)
|
|
855
|
+
raise first_error
|
|
856
|
+
else:
|
|
857
|
+
# No proxies were configured, just re-raise the original error
|
|
858
|
+
raise first_error
|
|
859
|
+
|
|
860
|
+
|
|
861
|
+
def create_auto_retry_session(max_proxy_attempts: int = 2) -> requests.Session:
|
|
862
|
+
"""
|
|
863
|
+
Create a requests.Session with automatic proxy retry functionality.
|
|
864
|
+
|
|
865
|
+
Args:
|
|
866
|
+
max_proxy_attempts: Maximum number of proxy attempts before falling back
|
|
867
|
+
|
|
868
|
+
Returns:
|
|
869
|
+
requests.Session: Session with auto-retry functionality
|
|
870
|
+
"""
|
|
871
|
+
session = requests.Session()
|
|
872
|
+
|
|
873
|
+
# Get initial proxy configuration
|
|
874
|
+
proxy_url = get_auto_proxy()
|
|
875
|
+
if proxy_url:
|
|
876
|
+
proxies = get_proxy_dict(proxy_url)
|
|
877
|
+
session.proxies.update(proxies)
|
|
878
|
+
|
|
879
|
+
# Store the max_proxy_attempts for use in retry logic
|
|
880
|
+
session._max_proxy_attempts = max_proxy_attempts
|
|
881
|
+
|
|
882
|
+
# Override the request method to add auto-retry functionality
|
|
883
|
+
original_request = session.request
|
|
884
|
+
|
|
885
|
+
def request_with_retry(method, url, **kwargs):
|
|
886
|
+
return make_request_with_auto_retry(
|
|
887
|
+
method=method,
|
|
888
|
+
url=url,
|
|
889
|
+
session=session,
|
|
890
|
+
max_proxy_attempts=max_proxy_attempts,
|
|
891
|
+
**kwargs
|
|
892
|
+
)
|
|
893
|
+
|
|
894
|
+
session.request = request_with_retry
|
|
895
|
+
return session
|
|
896
|
+
|
|
897
|
+
|
|
898
|
+
def enable_auto_retry_for_provider(provider_instance, max_proxy_attempts: int = 2):
|
|
899
|
+
"""
|
|
900
|
+
Enable auto-retry functionality for an existing provider instance.
|
|
901
|
+
|
|
902
|
+
This function can be used to add auto-retry functionality to providers
|
|
903
|
+
that were created without it, or to update the max_proxy_attempts setting.
|
|
904
|
+
|
|
905
|
+
Args:
|
|
906
|
+
provider_instance: The provider instance to enable auto-retry for
|
|
907
|
+
max_proxy_attempts: Maximum number of proxy attempts before falling back
|
|
908
|
+
"""
|
|
909
|
+
# Set the max proxy attempts
|
|
910
|
+
provider_instance._max_proxy_attempts = max_proxy_attempts
|
|
911
|
+
|
|
912
|
+
# Get current proxies or empty dict
|
|
913
|
+
current_proxies = getattr(provider_instance, 'proxies', {})
|
|
914
|
+
|
|
915
|
+
# Patch all existing sessions
|
|
916
|
+
_patch_instance_sessions(provider_instance, current_proxies)
|
|
917
|
+
|
|
918
|
+
# Add helper methods if they don't exist
|
|
919
|
+
if not hasattr(provider_instance, 'get_auto_retry_session'):
|
|
920
|
+
_add_proxy_helpers(provider_instance, current_proxies)
|
|
921
|
+
|
|
922
|
+
|
|
923
|
+
def disable_auto_retry_for_provider(provider_instance):
|
|
924
|
+
"""
|
|
925
|
+
Disable auto-retry functionality for a provider instance.
|
|
926
|
+
|
|
927
|
+
This will restore the original request methods for all sessions.
|
|
928
|
+
Note: This is a best-effort approach and may not work for all session types.
|
|
929
|
+
|
|
930
|
+
Args:
|
|
931
|
+
provider_instance: The provider instance to disable auto-retry for
|
|
932
|
+
"""
|
|
933
|
+
for attr_name in dir(provider_instance):
|
|
934
|
+
if attr_name.startswith('_'):
|
|
935
|
+
continue
|
|
936
|
+
|
|
937
|
+
try:
|
|
938
|
+
attr_obj = getattr(provider_instance, attr_name)
|
|
939
|
+
|
|
940
|
+
# Remove auto-retry from requests.Session objects
|
|
941
|
+
if isinstance(attr_obj, requests.Session) and hasattr(attr_obj, '_auto_retry_patched'):
|
|
942
|
+
# This is a simplified approach - in practice, restoring original methods
|
|
943
|
+
# would require storing references to them, which we don't do here
|
|
944
|
+
delattr(attr_obj, '_auto_retry_patched')
|
|
945
|
+
|
|
946
|
+
# Similar for other session types
|
|
947
|
+
elif httpx and isinstance(attr_obj, httpx.Client) and hasattr(attr_obj, '_auto_retry_patched'):
|
|
948
|
+
delattr(attr_obj, '_auto_retry_patched')
|
|
949
|
+
|
|
950
|
+
elif CurlSession and isinstance(attr_obj, CurlSession) and hasattr(attr_obj, '_auto_retry_patched'):
|
|
951
|
+
delattr(attr_obj, '_auto_retry_patched')
|
|
952
|
+
|
|
953
|
+
elif CurlAsyncSession and isinstance(attr_obj, CurlAsyncSession) and hasattr(attr_obj, '_auto_retry_patched'):
|
|
954
|
+
delattr(attr_obj, '_auto_retry_patched')
|
|
955
|
+
|
|
956
|
+
except Exception:
|
|
957
|
+
continue
|
|
958
|
+
|
|
959
|
+
|
|
960
|
+
def proxy():
|
|
961
|
+
"""
|
|
962
|
+
Return a working proxy dict or None. One-liner for easy use.
|
|
963
|
+
Example:
|
|
964
|
+
proxies = autoproxy.proxy()
|
|
965
|
+
requests.get(url, proxies=proxies)
|
|
966
|
+
"""
|
|
967
|
+
proxy_url = get_working_proxy()
|
|
968
|
+
return get_proxy_dict(proxy_url) if proxy_url else None
|
|
969
|
+
|
|
970
|
+
|
|
971
|
+
def patch(obj, proxy_url=None):
|
|
972
|
+
"""
|
|
973
|
+
Patch a function, class, or object to use proxies automatically.
|
|
974
|
+
- For functions: inject proxies kwarg if not present.
|
|
975
|
+
- For requests.Session: set .proxies.
|
|
976
|
+
- For classes: patch all methods that look like HTTP calls.
|
|
977
|
+
"""
|
|
978
|
+
if isinstance(obj, requests.Session):
|
|
979
|
+
obj.proxies.update(get_proxy_dict(proxy_url))
|
|
980
|
+
return obj
|
|
981
|
+
if httpx and isinstance(obj, httpx.Client):
|
|
982
|
+
obj._proxies = get_proxy_dict(proxy_url)
|
|
983
|
+
return obj
|
|
984
|
+
if isinstance(obj, types.FunctionType):
|
|
985
|
+
def wrapper(*args, **kwargs):
|
|
986
|
+
if 'proxies' not in kwargs:
|
|
987
|
+
kwargs['proxies'] = get_proxy_dict(proxy_url)
|
|
988
|
+
return obj(*args, **kwargs)
|
|
989
|
+
return wrapper
|
|
990
|
+
if isinstance(obj, type): # class
|
|
991
|
+
for attr in dir(obj):
|
|
992
|
+
if attr.startswith('get') or attr.startswith('post'):
|
|
993
|
+
method = getattr(obj, attr)
|
|
994
|
+
if callable(method):
|
|
995
|
+
setattr(obj, attr, patch(method, proxy_url))
|
|
996
|
+
return obj
|
|
997
|
+
# fallback: return as is
|
|
998
|
+
return obj
|
|
999
|
+
|
|
1000
|
+
|
|
1001
|
+
@contextmanager
|
|
1002
|
+
def use_proxy(proxy_url=None):
|
|
1003
|
+
"""
|
|
1004
|
+
Context manager to temporarily patch requests and httpx to use a proxy globally.
|
|
1005
|
+
Example:
|
|
1006
|
+
with autoproxy.use_proxy():
|
|
1007
|
+
requests.get(url) # uses proxy automatically
|
|
1008
|
+
"""
|
|
1009
|
+
orig_request = requests.Session.request
|
|
1010
|
+
def request_with_proxy(self, method, url, **kwargs):
|
|
1011
|
+
if 'proxies' not in kwargs:
|
|
1012
|
+
kwargs['proxies'] = get_proxy_dict(proxy_url)
|
|
1013
|
+
return orig_request(self, method, url, **kwargs)
|
|
1014
|
+
requests.Session.request = request_with_proxy
|
|
1015
|
+
# Optionally patch httpx if available
|
|
1016
|
+
orig_httpx = None
|
|
1017
|
+
if httpx:
|
|
1018
|
+
orig_httpx = httpx.Client.request
|
|
1019
|
+
def httpx_request_with_proxy(self, method, url, **kwargs):
|
|
1020
|
+
if 'proxies' not in kwargs:
|
|
1021
|
+
kwargs['proxies'] = get_proxy_dict(proxy_url)
|
|
1022
|
+
return orig_httpx(self, method, url, **kwargs)
|
|
1023
|
+
httpx.Client.request = httpx_request_with_proxy
|
|
1024
|
+
try:
|
|
1025
|
+
yield
|
|
1026
|
+
finally:
|
|
1027
|
+
requests.Session.request = orig_request
|
|
1028
|
+
if httpx and orig_httpx:
|
|
1029
|
+
httpx.Client.request = orig_httpx
|
|
1030
|
+
|
|
1031
|
+
|
|
1032
|
+
def proxyify(func):
|
|
1033
|
+
"""
|
|
1034
|
+
Decorator to auto-inject proxies into any function.
|
|
1035
|
+
Example:
|
|
1036
|
+
@autoproxy.proxyify
|
|
1037
|
+
def my_request(...): ...
|
|
1038
|
+
"""
|
|
1039
|
+
def wrapper(*args, **kwargs):
|
|
1040
|
+
if 'proxies' not in kwargs:
|
|
1041
|
+
kwargs['proxies'] = proxy()
|
|
1042
|
+
return func(*args, **kwargs)
|
|
1043
|
+
return wrapper
|
|
1044
|
+
|
|
1045
|
+
|
|
1046
|
+
def list_proxies():
|
|
1047
|
+
"""
|
|
1048
|
+
List all available proxies (Webshare, remote, NordVPN).
|
|
1049
|
+
"""
|
|
1050
|
+
return get_cached_proxies()
|
|
1051
|
+
|
|
1052
|
+
|
|
1053
|
+
def test_all_proxies(timeout=5):
|
|
1054
|
+
"""
|
|
1055
|
+
Test all proxies and return a dict of proxy_url: True/False.
|
|
1056
|
+
"""
|
|
1057
|
+
results = {}
|
|
1058
|
+
for proxy in get_cached_proxies():
|
|
1059
|
+
results[proxy] = test_proxy(proxy, timeout=timeout)
|
|
1060
|
+
return results
|
|
1061
|
+
|
|
1062
|
+
|
|
1063
|
+
def current_proxy():
|
|
1064
|
+
"""
|
|
1065
|
+
Return a random proxy that would be used now (Webshare preferred).
|
|
1066
|
+
"""
|
|
1067
|
+
return get_auto_proxy()
|