jupyterlite-simple-cors-proxy 0.1.5__py3-none-any.whl → 0.1.7__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,6 @@
1
1
  # File: jupyterlite_simple_cors_proxy/__init__.py
2
- from .proxy import cors_proxy, robust_get_request
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.2"
5
- __all__ = ["cors_proxy_get", "robust_get_request"]
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
@@ -6,23 +6,24 @@ import io
6
6
  import platform
7
7
  PLATFORM = platform.system().lower()
8
8
 
9
- def xurl(url, params=None):
10
- if PLATFORM=="emscripten":
9
+ def xurl(url, params=None, force=False):
10
+ """Generate a proxied URL."""
11
+ if PLATFORM=="emscripten" or force:
11
12
  if params:
12
13
  url = f"{url}?{urlencode(params)}"
13
14
  url = f"https://corsproxy.io/{quote(url)}"
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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: jupyterlite-simple-cors-proxy
3
- Version: 0.1.5
3
+ Version: 0.1.7
4
4
  Summary: A simple CORS proxy utility with requests-like response
5
5
  Home-page: https://github.com/innovationOUtside/jupyterlite-simple-cors-proxy
6
6
  Author: Tony Hirst
@@ -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=F1Y3oZFLKGV_q2B90txW9Lvxw8F9EyAEdnuk0WSeNiA,174
2
- jupyterlite_simple_cors_proxy/proxy.py,sha256=eE7K9qvfIdW7NiW_I9Q4cruyZyIHE9QEBBchHxxhNcw,1214
3
- jupyterlite_simple_cors_proxy-0.1.5.dist-info/LICENSE,sha256=Ogw7GUmeZIxmDNiKWsu_N07svNoGnPB7lWyiXHX_rMY,1074
4
- jupyterlite_simple_cors_proxy-0.1.5.dist-info/METADATA,sha256=NG7Bw00dVZX9Yh0CPOxJ0yznuuX_QkNCnwD9xKz8b5w,1682
5
- jupyterlite_simple_cors_proxy-0.1.5.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
6
- jupyterlite_simple_cors_proxy-0.1.5.dist-info/top_level.txt,sha256=Oh0oQrSmRnBq5u675coiKMbkb2ASg8AGF8ZiZTzUS5Q,30
7
- jupyterlite_simple_cors_proxy-0.1.5.dist-info/RECORD,,