jupyterlite-simple-cors-proxy 0.1.6__py3-none-any.whl → 0.1.7__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.
- jupyterlite_simple_cors_proxy/__init__.py +3 -2
- jupyterlite_simple_cors_proxy/fastf1_proxy.py +212 -0
- jupyterlite_simple_cors_proxy/proxy.py +7 -5
- {jupyterlite_simple_cors_proxy-0.1.6.dist-info → jupyterlite_simple_cors_proxy-0.1.7.dist-info}/METADATA +1 -1
- jupyterlite_simple_cors_proxy-0.1.7.dist-info/RECORD +8 -0
- jupyterlite_simple_cors_proxy-0.1.6.dist-info/RECORD +0 -7
- {jupyterlite_simple_cors_proxy-0.1.6.dist-info → jupyterlite_simple_cors_proxy-0.1.7.dist-info}/LICENSE +0 -0
- {jupyterlite_simple_cors_proxy-0.1.6.dist-info → jupyterlite_simple_cors_proxy-0.1.7.dist-info}/WHEEL +0 -0
- {jupyterlite_simple_cors_proxy-0.1.6.dist-info → jupyterlite_simple_cors_proxy-0.1.7.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,6 @@
|
|
1
1
|
# File: jupyterlite_simple_cors_proxy/__init__.py
|
2
2
|
from .proxy import cors_proxy_get, robust_get_request, xurl, furl
|
3
|
+
from .fastf1_proxy import enable_cors_proxy as fastf1_cors_proxy
|
3
4
|
|
4
|
-
__version__ = "0.1.
|
5
|
-
__all__ = ["cors_proxy_get", "robust_get_request", "xurl", "furl"]
|
5
|
+
__version__ = "0.1.7"
|
6
|
+
__all__ = ["cors_proxy_get", "robust_get_request", "xurl", "furl", "fastf1_cors_proxy"]
|
@@ -0,0 +1,212 @@
|
|
1
|
+
#import functools
|
2
|
+
from urllib.parse import urlparse, quote
|
3
|
+
import requests
|
4
|
+
#import requests_cache
|
5
|
+
#from requests_cache.session import CachedSession
|
6
|
+
import fastf1
|
7
|
+
import logging
|
8
|
+
from typing import List, Optional, Dict, Any
|
9
|
+
from dataclasses import dataclass
|
10
|
+
|
11
|
+
@dataclass
|
12
|
+
class ProxyConfig:
|
13
|
+
"""Configuration for the CORS proxy."""
|
14
|
+
proxy_url: str = "https://api.allorigins.win/raw?url="
|
15
|
+
domains: List[str] = None
|
16
|
+
debug: bool = False
|
17
|
+
retry_count: int = 3
|
18
|
+
timeout: int = 30
|
19
|
+
|
20
|
+
class CORSProxyPatcher:
|
21
|
+
"""Patches FastF1 to handle CORS requests through a proxy service."""
|
22
|
+
|
23
|
+
def __init__(self, config: ProxyConfig = None):
|
24
|
+
"""
|
25
|
+
Initialize the CORS proxy patcher for FastF1.
|
26
|
+
|
27
|
+
Args:
|
28
|
+
config (ProxyConfig): Configuration object for the proxy
|
29
|
+
"""
|
30
|
+
self.config = config or ProxyConfig()
|
31
|
+
self.domains = self.config.domains or []
|
32
|
+
|
33
|
+
self._setup_logging()
|
34
|
+
self._setup_session()
|
35
|
+
|
36
|
+
def _setup_logging(self) -> None:
|
37
|
+
"""Configure logging based on debug setting."""
|
38
|
+
self.logger = logging.getLogger('CORSProxyPatcher')
|
39
|
+
if self.config.debug:
|
40
|
+
logging.basicConfig(
|
41
|
+
level=logging.DEBUG,
|
42
|
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
43
|
+
)
|
44
|
+
|
45
|
+
def _setup_session(self) -> None:
|
46
|
+
"""Set up the requests session with retry functionality."""
|
47
|
+
self.session = requests.Session()
|
48
|
+
retry_strategy = requests.adapters.Retry(
|
49
|
+
total=self.config.retry_count,
|
50
|
+
backoff_factor=0.5,
|
51
|
+
status_forcelist=[500, 502, 503, 504]
|
52
|
+
)
|
53
|
+
adapter = requests.adapters.HTTPAdapter(max_retries=retry_strategy)
|
54
|
+
self.session.mount("http://", adapter)
|
55
|
+
self.session.mount("https://", adapter)
|
56
|
+
|
57
|
+
def should_proxy(self, url: str) -> bool:
|
58
|
+
"""
|
59
|
+
Check if the URL should be routed through proxy based on domain.
|
60
|
+
|
61
|
+
Args:
|
62
|
+
url (str): URL to check
|
63
|
+
|
64
|
+
Returns:
|
65
|
+
bool: True if URL should be proxied
|
66
|
+
"""
|
67
|
+
parsed = urlparse(url)
|
68
|
+
should_proxy = any(domain in parsed.netloc for domain in self.domains)
|
69
|
+
if self.config.debug:
|
70
|
+
self.logger.debug(f"URL: {url} - Should proxy: {should_proxy}")
|
71
|
+
return should_proxy
|
72
|
+
|
73
|
+
def get_proxied_url(self, url: str) -> str:
|
74
|
+
"""
|
75
|
+
Get the proxied version of the URL if needed.
|
76
|
+
|
77
|
+
Args:
|
78
|
+
url (str): Original URL
|
79
|
+
|
80
|
+
Returns:
|
81
|
+
str: Proxied URL if needed, original URL otherwise
|
82
|
+
"""
|
83
|
+
if self.should_proxy(url):
|
84
|
+
if 'allorigins' in self.config.proxy_url:
|
85
|
+
proxied = f"{self.config.proxy_url}{quote(url, safe='')}"
|
86
|
+
else:
|
87
|
+
proxied = f"{self.config.proxy_url}{url}"
|
88
|
+
if self.config.debug:
|
89
|
+
self.logger.debug(f"Original URL: {url}")
|
90
|
+
self.logger.debug(f"Proxied URL: {proxied}")
|
91
|
+
return proxied
|
92
|
+
return url
|
93
|
+
|
94
|
+
def modify_headers(self, headers: Optional[Dict[str, str]] = None) -> Dict[str, str]:
|
95
|
+
"""
|
96
|
+
Modify request headers to handle CORS.
|
97
|
+
|
98
|
+
Args:
|
99
|
+
headers (dict, optional): Original headers
|
100
|
+
|
101
|
+
Returns:
|
102
|
+
dict: Modified headers
|
103
|
+
"""
|
104
|
+
modified_headers = headers.copy() if headers else {}
|
105
|
+
modified_headers.update({
|
106
|
+
'Origin': 'null',
|
107
|
+
'Sec-Fetch-Mode': 'cors',
|
108
|
+
'Sec-Fetch-Site': 'cross-site',
|
109
|
+
'Accept': 'application/json, text/plain, */*',
|
110
|
+
'User-Agent': 'Mozilla/5.0 (compatible; FastF1/Python)'
|
111
|
+
})
|
112
|
+
return modified_headers
|
113
|
+
|
114
|
+
def log_response(self, response: requests.Response, url: str) -> None:
|
115
|
+
"""
|
116
|
+
Log response details for debugging.
|
117
|
+
|
118
|
+
Args:
|
119
|
+
response (Response): Response object
|
120
|
+
url (str): Original URL
|
121
|
+
"""
|
122
|
+
if self.config.debug:
|
123
|
+
self.logger.debug(f"\nRequest to: {url}")
|
124
|
+
self.logger.debug(f"Status Code: {response.status_code}")
|
125
|
+
self.logger.debug(f"Headers: {dict(response.headers)}")
|
126
|
+
try:
|
127
|
+
self.logger.debug(f"Response Text: {response.text[:500]}...")
|
128
|
+
except Exception as e:
|
129
|
+
self.logger.debug(f"Couldn't read response text: {e}")
|
130
|
+
|
131
|
+
def make_request(self, method: str, url: str, headers: Optional[Dict[str, str]] = None,
|
132
|
+
**kwargs: Any) -> requests.Response:
|
133
|
+
"""
|
134
|
+
Make an HTTP request with proper error handling and logging.
|
135
|
+
|
136
|
+
Args:
|
137
|
+
method (str): HTTP method ('get' or 'post')
|
138
|
+
url (str): URL to request
|
139
|
+
headers (dict, optional): Request headers
|
140
|
+
**kwargs: Additional request parameters
|
141
|
+
|
142
|
+
Returns:
|
143
|
+
Response: Response object
|
144
|
+
|
145
|
+
Raises:
|
146
|
+
requests.exceptions.RequestException: If request fails
|
147
|
+
"""
|
148
|
+
proxied_url = self.get_proxied_url(url)
|
149
|
+
modified_headers = self.modify_headers(headers)
|
150
|
+
kwargs['headers'] = modified_headers
|
151
|
+
kwargs['timeout'] = kwargs.get('timeout', self.config.timeout)
|
152
|
+
|
153
|
+
try:
|
154
|
+
if fastf1.Cache._requests_session_cached and not fastf1.Cache._tmp_disabled:
|
155
|
+
session = fastf1.Cache._requests_session_cached
|
156
|
+
else:
|
157
|
+
session = self.session
|
158
|
+
|
159
|
+
response = getattr(session, method)(proxied_url, **kwargs)
|
160
|
+
response.raise_for_status()
|
161
|
+
|
162
|
+
self.log_response(response, url)
|
163
|
+
return response
|
164
|
+
|
165
|
+
except requests.exceptions.RequestException as e:
|
166
|
+
if self.config.debug:
|
167
|
+
self.logger.error(f"Request failed: {str(e)}")
|
168
|
+
raise
|
169
|
+
|
170
|
+
def patch_fastf1(self) -> None:
|
171
|
+
"""Patch FastF1's request methods to use CORS proxy."""
|
172
|
+
def wrapped_get(cls, url: str, headers: Optional[Dict[str, str]] = None, **kwargs: Any) -> requests.Response:
|
173
|
+
return self.make_request('get', url, headers, **kwargs)
|
174
|
+
|
175
|
+
def wrapped_post(cls, url: str, headers: Optional[Dict[str, str]] = None, **kwargs: Any) -> requests.Response:
|
176
|
+
return self.make_request('post', url, headers, **kwargs)
|
177
|
+
|
178
|
+
fastf1.Cache.requests_get = classmethod(wrapped_get)
|
179
|
+
fastf1.Cache.requests_post = classmethod(wrapped_post)
|
180
|
+
|
181
|
+
def enable_cors_proxy(
|
182
|
+
domains: List[str],
|
183
|
+
proxy_url: Optional[str] = None,
|
184
|
+
debug: bool = False,
|
185
|
+
retry_count: int = 3,
|
186
|
+
timeout: int = 30
|
187
|
+
) -> CORSProxyPatcher:
|
188
|
+
"""
|
189
|
+
Enable CORS proxy support for FastF1.
|
190
|
+
|
191
|
+
Args:
|
192
|
+
domains (list): List of domains to route through the proxy
|
193
|
+
proxy_url (str, optional): Base URL of the CORS proxy service
|
194
|
+
debug (bool): Enable debug logging
|
195
|
+
retry_count (int): Number of retry attempts for failed requests
|
196
|
+
timeout (int): Request timeout in seconds
|
197
|
+
|
198
|
+
Returns:
|
199
|
+
CORSProxyPatcher: Configured proxy patcher instance
|
200
|
+
"""
|
201
|
+
config = ProxyConfig(
|
202
|
+
proxy_url=proxy_url or "https://api.allorigins.win/raw?url=",
|
203
|
+
domains=domains,
|
204
|
+
debug=debug,
|
205
|
+
retry_count=retry_count,
|
206
|
+
timeout=timeout
|
207
|
+
)
|
208
|
+
|
209
|
+
patcher = CORSProxyPatcher(config)
|
210
|
+
patcher.patch_fastf1()
|
211
|
+
|
212
|
+
return patcher
|
@@ -7,6 +7,7 @@ import platform
|
|
7
7
|
PLATFORM = platform.system().lower()
|
8
8
|
|
9
9
|
def xurl(url, params=None, force=False):
|
10
|
+
"""Generate a proxied URL."""
|
10
11
|
if PLATFORM=="emscripten" or force:
|
11
12
|
if params:
|
12
13
|
url = f"{url}?{urlencode(params)}"
|
@@ -14,15 +15,15 @@ def xurl(url, params=None, force=False):
|
|
14
15
|
|
15
16
|
return url
|
16
17
|
|
17
|
-
def furl(url, params=None):
|
18
|
-
"""Return file like object."""
|
19
|
-
r = cors_proxy_get(url, params)
|
18
|
+
def furl(url, params=None, force=False):
|
19
|
+
"""Return file like object after calling the proxied URL."""
|
20
|
+
r = cors_proxy_get(url, params, force)
|
20
21
|
|
21
22
|
# Return a file-like object from the JSON string
|
22
23
|
return io.BytesIO(r.content)
|
23
24
|
|
24
25
|
|
25
|
-
def cors_proxy_get(url, params=None):
|
26
|
+
def cors_proxy_get(url, params=None, force=False):
|
26
27
|
"""
|
27
28
|
CORS proxy for GET resources with requests-like response.
|
28
29
|
|
@@ -33,12 +34,13 @@ def cors_proxy_get(url, params=None):
|
|
33
34
|
Returns:
|
34
35
|
A requests response object.
|
35
36
|
"""
|
36
|
-
proxy_url = xurl(url, params)
|
37
|
+
proxy_url = xurl(url, params, force)
|
37
38
|
|
38
39
|
# Do a simple requests get and
|
39
40
|
# just pass through the entire response object
|
40
41
|
return requests.get(proxy_url)
|
41
42
|
|
43
|
+
|
42
44
|
def robust_get_request(url, params=None):
|
43
45
|
"""
|
44
46
|
Try to make a simple request else fall back to a proxy.
|
@@ -0,0 +1,8 @@
|
|
1
|
+
jupyterlite_simple_cors_proxy/__init__.py,sha256=4IEZXGJ78nCMkOujvGFjwgNf3Rmv77u_lkZWufob5wU,292
|
2
|
+
jupyterlite_simple_cors_proxy/fastf1_proxy.py,sha256=zsLspQjuj9gw4qbsFRydZvmfLelXUmb3gUOXjrGzQgs,7505
|
3
|
+
jupyterlite_simple_cors_proxy/proxy.py,sha256=hF7SeLx7whBkE8m8bDUaelx2tKfLrSHA_Lp_auFtqeY,1341
|
4
|
+
jupyterlite_simple_cors_proxy-0.1.7.dist-info/LICENSE,sha256=Ogw7GUmeZIxmDNiKWsu_N07svNoGnPB7lWyiXHX_rMY,1074
|
5
|
+
jupyterlite_simple_cors_proxy-0.1.7.dist-info/METADATA,sha256=oXlGf3Dux_i1RHxUOF5IUOUqNECoZ-4EkLUkTn1UsWc,1682
|
6
|
+
jupyterlite_simple_cors_proxy-0.1.7.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
7
|
+
jupyterlite_simple_cors_proxy-0.1.7.dist-info/top_level.txt,sha256=Oh0oQrSmRnBq5u675coiKMbkb2ASg8AGF8ZiZTzUS5Q,30
|
8
|
+
jupyterlite_simple_cors_proxy-0.1.7.dist-info/RECORD,,
|
@@ -1,7 +0,0 @@
|
|
1
|
-
jupyterlite_simple_cors_proxy/__init__.py,sha256=LLiWTvpFRjtUNxEgpzSWkI4CAXIBtR1XlXgBc-gkkcw,206
|
2
|
-
jupyterlite_simple_cors_proxy/proxy.py,sha256=d7SF-HszSUA3DemI04UHS8c5bJ2XXshPGD_ngUEe3-s,1236
|
3
|
-
jupyterlite_simple_cors_proxy-0.1.6.dist-info/LICENSE,sha256=Ogw7GUmeZIxmDNiKWsu_N07svNoGnPB7lWyiXHX_rMY,1074
|
4
|
-
jupyterlite_simple_cors_proxy-0.1.6.dist-info/METADATA,sha256=aIrUBWiyExB8LxIvsZX5S-TJcXvPw5tM8HIXRiWqEkg,1682
|
5
|
-
jupyterlite_simple_cors_proxy-0.1.6.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
6
|
-
jupyterlite_simple_cors_proxy-0.1.6.dist-info/top_level.txt,sha256=Oh0oQrSmRnBq5u675coiKMbkb2ASg8AGF8ZiZTzUS5Q,30
|
7
|
-
jupyterlite_simple_cors_proxy-0.1.6.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|