bas-http 1.0.1__tar.gz → 1.0.3__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.
- bas_http-1.0.3/PKG-INFO +532 -0
- {bas_http-1.0.1 → bas_http-1.0.3}/bas/README.md +4 -4
- {bas_http-1.0.1 → bas_http-1.0.3}/bas/__init__.py +1 -1
- {bas_http-1.0.1 → bas_http-1.0.3}/bas/_curl.py +1 -1
- {bas_http-1.0.1 → bas_http-1.0.3}/bas/cookies.py +2 -2
- {bas_http-1.0.1 → bas_http-1.0.3}/bas/impersonate.py +3 -3
- {bas_http-1.0.1 → bas_http-1.0.3}/bas/session.py +5 -5
- {bas_http-1.0.1 → bas_http-1.0.3}/bas/tests/test_cookies.py +6 -6
- {bas_http-1.0.1 → bas_http-1.0.3}/bas/tests/test_real_request.py +3 -3
- bas_http-1.0.3/bas_http.egg-info/PKG-INFO +532 -0
- {bas_http-1.0.1 → bas_http-1.0.3}/pyproject.toml +8 -9
- bas_http-1.0.1/PKG-INFO +0 -25
- bas_http-1.0.1/bas_http.egg-info/PKG-INFO +0 -25
- {bas_http-1.0.1 → bas_http-1.0.3}/bas/async_session.py +0 -0
- {bas_http-1.0.1 → bas_http-1.0.3}/bas/curl_parser.py +0 -0
- {bas_http-1.0.1 → bas_http-1.0.3}/bas/models.py +0 -0
- {bas_http-1.0.1 → bas_http-1.0.3}/bas/simple_session.py +0 -0
- {bas_http-1.0.1 → bas_http-1.0.3}/bas/tests/__init__.py +0 -0
- {bas_http-1.0.1 → bas_http-1.0.3}/bas/tests/test_curl_parser.py +0 -0
- {bas_http-1.0.1 → bas_http-1.0.3}/bas/tests/test_spaceshooter.py +0 -0
- {bas_http-1.0.1 → bas_http-1.0.3}/bas_http.egg-info/SOURCES.txt +0 -0
- {bas_http-1.0.1 → bas_http-1.0.3}/bas_http.egg-info/dependency_links.txt +0 -0
- {bas_http-1.0.1 → bas_http-1.0.3}/bas_http.egg-info/top_level.txt +0 -0
- {bas_http-1.0.1 → bas_http-1.0.3}/setup.cfg +0 -0
bas_http-1.0.3/PKG-INFO
ADDED
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bas-http
|
|
3
|
+
Version: 1.0.3
|
|
4
|
+
Summary: Copy-paste your browser's request, bas handles the rest. No impersonation needed. Zero dependencies.
|
|
5
|
+
Author: bas
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://pypi.org/project/bas-http/
|
|
8
|
+
Project-URL: Repository, https://pypi.org/project/bas-http/
|
|
9
|
+
Project-URL: Issues, https://pypi.org/project/bas-http/
|
|
10
|
+
Keywords: http,curl,cookie,scraping,browser,devtools,requests
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Requires-Python: >=3.8
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# bas
|
|
27
|
+
|
|
28
|
+
Copy-paste your browser's request, bas handles the rest.
|
|
29
|
+
|
|
30
|
+
No impersonation needed. No fingerprint chasing. No broken cookies. Just grab your headers + cookies from DevTools and go.
|
|
31
|
+
|
|
32
|
+
## Why bas?
|
|
33
|
+
|
|
34
|
+
| Problem | bas | bas |
|
|
35
|
+
|---------|----------|-----|
|
|
36
|
+
| Cookies lost during redirects | **BUG** | **Fixed** |
|
|
37
|
+
| Cookies not accumulating across requests | **BUG** | **Fixed** |
|
|
38
|
+
| Manual Cookie header construction | Yes | **Auto-injected** |
|
|
39
|
+
| Requires curl-impersonate | Yes | **No — pure Python** |
|
|
40
|
+
| Browser fingerprint updates | Constant chase | **None — bring your own headers** |
|
|
41
|
+
| External dependencies | Yes | **Zero** |
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install bas-http
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Quick Start
|
|
50
|
+
|
|
51
|
+
### Method 1: Paste a curl command from DevTools (Recommended)
|
|
52
|
+
|
|
53
|
+
This is the fastest way. Copy a curl command from your browser and bas does the rest.
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
1. Open browser DevTools (F12) → Network tab
|
|
57
|
+
2. Make a request on the website
|
|
58
|
+
3. Right-click the request → "Copy as cURL"
|
|
59
|
+
4. Paste into Python
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
import bas
|
|
64
|
+
|
|
65
|
+
# Paste your curl command (use r'' raw string to preserve backslashes)
|
|
66
|
+
s = bas.from_curl(r'''curl "https://example.com/page" ^
|
|
67
|
+
-H "accept: text/html,application/xhtml+xml" ^
|
|
68
|
+
-H "accept-language: en-US,en;q=0.9" ^
|
|
69
|
+
-H "user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36" ^
|
|
70
|
+
-b "cf_clearance=abc123; session_id=xyz; token=def456"''')
|
|
71
|
+
|
|
72
|
+
# Now make requests — all headers and cookies are auto-injected
|
|
73
|
+
r = s.get("https://example.com/page")
|
|
74
|
+
print(r.status_code)
|
|
75
|
+
print(r.text)
|
|
76
|
+
|
|
77
|
+
# Follow-up requests keep the same headers and cookies
|
|
78
|
+
r2 = s.get("https://example.com/dashboard")
|
|
79
|
+
print(r2.status_code)
|
|
80
|
+
|
|
81
|
+
# Add more cookies manually if needed
|
|
82
|
+
s.set_cookie("new_cookie", "value", domain="example.com")
|
|
83
|
+
r3 = s.get("https://example.com/api/data")
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Method 2: Build headers manually
|
|
87
|
+
|
|
88
|
+
If you have headers copied from DevTools (not as a curl command):
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
import bas
|
|
92
|
+
|
|
93
|
+
s = bas.Session()
|
|
94
|
+
|
|
95
|
+
# Paste your headers from DevTools → Network → Headers tab
|
|
96
|
+
s.headers = {
|
|
97
|
+
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
|
|
98
|
+
"accept-language": "en-US,en;q=0.9",
|
|
99
|
+
"cache-control": "max-age=0",
|
|
100
|
+
"sec-ch-ua": '"Chromium";v="137", "Not/A)Brand";v="24"',
|
|
101
|
+
"sec-ch-ua-mobile": "?0",
|
|
102
|
+
"sec-ch-ua-platform": '"Windows"',
|
|
103
|
+
"sec-fetch-dest": "document",
|
|
104
|
+
"sec-fetch-mode": "navigate",
|
|
105
|
+
"sec-fetch-site": "none",
|
|
106
|
+
"sec-fetch-user": "?1",
|
|
107
|
+
"upgrade-insecure-requests": "1",
|
|
108
|
+
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36",
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
# Add your cookies from DevTools → Application → Cookies
|
|
112
|
+
s.set_cookie("cf_clearance", "abc123", domain="example.com")
|
|
113
|
+
s.set_cookie("session_id", "xyz", domain="example.com")
|
|
114
|
+
s.set_cookie("csrf_token", "def456", domain="example.com")
|
|
115
|
+
|
|
116
|
+
# Go!
|
|
117
|
+
r = s.get("https://example.com/page")
|
|
118
|
+
print(r.status_code, len(r.text))
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Method 3: Headers + cookies in one call
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
import bas
|
|
125
|
+
|
|
126
|
+
s = bas.from_headers(
|
|
127
|
+
url="https://example.com",
|
|
128
|
+
headers={
|
|
129
|
+
"user-agent": "Mozilla/5.0 ...",
|
|
130
|
+
"accept": "text/html,...",
|
|
131
|
+
},
|
|
132
|
+
cookies={
|
|
133
|
+
"cf_clearance": "abc123",
|
|
134
|
+
"session": "xyz",
|
|
135
|
+
},
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
r = s.get("https://example.com/page")
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## HTTP Methods
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
import bas
|
|
145
|
+
|
|
146
|
+
s = bas.from_curl(r'''curl "https://example.com" -b "session=abc"''')
|
|
147
|
+
|
|
148
|
+
# GET
|
|
149
|
+
r = s.get("https://example.com/page")
|
|
150
|
+
|
|
151
|
+
# POST with form data
|
|
152
|
+
r = s.post("https://example.com/login", data={"username": "user", "password": "pass"})
|
|
153
|
+
|
|
154
|
+
# POST with JSON
|
|
155
|
+
r = s.post("https://example.com/api", json={"key": "value"})
|
|
156
|
+
|
|
157
|
+
# PUT
|
|
158
|
+
r = s.put("https://example.com/api/123", json={"name": "updated"})
|
|
159
|
+
|
|
160
|
+
# DELETE
|
|
161
|
+
r = s.delete("https://example.com/api/123")
|
|
162
|
+
|
|
163
|
+
# PATCH
|
|
164
|
+
r = s.patch("https://example.com/api/123", json={"name": "patched"})
|
|
165
|
+
|
|
166
|
+
# HEAD
|
|
167
|
+
r = s.head("https://example.com/page")
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Response Object
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
r = s.get("https://example.com/page")
|
|
174
|
+
|
|
175
|
+
# Status code
|
|
176
|
+
print(r.status_code) # 200
|
|
177
|
+
print(r.ok) # True (status < 400)
|
|
178
|
+
print(r.reason_phrase) # "OK"
|
|
179
|
+
|
|
180
|
+
# Content
|
|
181
|
+
print(r.text) # Decoded text
|
|
182
|
+
print(r.body) # Raw bytes
|
|
183
|
+
print(r.json) # Parsed JSON
|
|
184
|
+
print(r.content_type) # "text/html; charset=utf-8"
|
|
185
|
+
|
|
186
|
+
# Headers
|
|
187
|
+
print(r.headers) # Case-insensitive headers dict
|
|
188
|
+
print(r.headers["content-type"])
|
|
189
|
+
|
|
190
|
+
# URL info
|
|
191
|
+
print(r.url) # Final URL (after redirects)
|
|
192
|
+
print(r.ip) # Server IP
|
|
193
|
+
print(r.http_version) # "1.1" or "2.0"
|
|
194
|
+
print(r.elapsed) # Response time in seconds
|
|
195
|
+
|
|
196
|
+
# Redirect history
|
|
197
|
+
for resp in r.history:
|
|
198
|
+
print(resp.status_code, resp.url)
|
|
199
|
+
|
|
200
|
+
# Raise exception on error
|
|
201
|
+
r.raise_for_status() # Raises HTTPError if status >= 400
|
|
202
|
+
|
|
203
|
+
# Iterate over content
|
|
204
|
+
for chunk in r.iter_content(chunk_size=8192):
|
|
205
|
+
process(chunk)
|
|
206
|
+
|
|
207
|
+
for line in r.iter_lines():
|
|
208
|
+
print(line)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Cookie Management
|
|
212
|
+
|
|
213
|
+
### Auto-injection
|
|
214
|
+
|
|
215
|
+
bas automatically injects cookies into every request. No manual header construction needed.
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
import bas
|
|
219
|
+
|
|
220
|
+
s = bas.from_curl(r'''curl "https://example.com" -b "session=abc123; token=xyz"''')
|
|
221
|
+
|
|
222
|
+
# This request automatically has Cookie: session=abc123; token=xyz
|
|
223
|
+
r = s.get("https://example.com/page")
|
|
224
|
+
|
|
225
|
+
# See what cookies were sent
|
|
226
|
+
print(r.request.headers.get("Cookie"))
|
|
227
|
+
# "session=abc123; token=xyz"
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Set cookies manually
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
s = bas.Session()
|
|
234
|
+
s.set_cookie("name", "value", domain="example.com", path="/")
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Get cookies for a URL
|
|
238
|
+
|
|
239
|
+
```python
|
|
240
|
+
cookies = s.get_cookies("https://example.com/page")
|
|
241
|
+
print(cookies) # {"session": "abc123", "token": "xyz"}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Clear all cookies
|
|
245
|
+
|
|
246
|
+
```python
|
|
247
|
+
s.clear_cookies()
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Save cookies to file
|
|
251
|
+
|
|
252
|
+
```python
|
|
253
|
+
# JSON format
|
|
254
|
+
s.save_cookies("cookies.json")
|
|
255
|
+
|
|
256
|
+
# Netscape format (compatible with curl/wget)
|
|
257
|
+
s.save_cookies("cookies.txt", format="netscape")
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Load cookies from file
|
|
261
|
+
|
|
262
|
+
```python
|
|
263
|
+
s.load_cookies("cookies.json")
|
|
264
|
+
s.load_cookies("cookies.txt", format="netscape")
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Cookie accumulation across requests
|
|
268
|
+
|
|
269
|
+
When the server sends Set-Cookie headers, bas automatically stores them:
|
|
270
|
+
|
|
271
|
+
```python
|
|
272
|
+
s = bas.Session()
|
|
273
|
+
|
|
274
|
+
# Server sets cookies in this response
|
|
275
|
+
r1 = s.get("https://example.com/login")
|
|
276
|
+
# Set-Cookie: session_id=abc123; Path=/
|
|
277
|
+
# Set-Cookie: user=john; Path=/
|
|
278
|
+
|
|
279
|
+
# These cookies are automatically sent with the next request
|
|
280
|
+
r2 = s.get("https://example.com/dashboard")
|
|
281
|
+
# Cookie: session_id=abc123; user=john
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Cookies survive redirects
|
|
285
|
+
|
|
286
|
+
```python
|
|
287
|
+
# Server redirects and sets more cookies
|
|
288
|
+
r = s.get("https://example.com/page")
|
|
289
|
+
# 302 → https://example.com/dashboard
|
|
290
|
+
# Set-Cookie: tracking=xyz; Path=/
|
|
291
|
+
|
|
292
|
+
# ALL cookies are preserved through redirects
|
|
293
|
+
# No cookies lost (unlike bas!)
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
## Session Persistence
|
|
297
|
+
|
|
298
|
+
Keep a session alive across multiple script runs:
|
|
299
|
+
|
|
300
|
+
```python
|
|
301
|
+
import bas
|
|
302
|
+
|
|
303
|
+
s = bas.Session()
|
|
304
|
+
|
|
305
|
+
# Try to load previous session
|
|
306
|
+
try:
|
|
307
|
+
s.load_cookies("my_session.json")
|
|
308
|
+
except FileNotFoundError:
|
|
309
|
+
pass
|
|
310
|
+
|
|
311
|
+
# Make requests
|
|
312
|
+
r = s.get("https://example.com/page")
|
|
313
|
+
|
|
314
|
+
# Save session for next run
|
|
315
|
+
s.save_cookies("my_session.json")
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
## Parse curl commands
|
|
319
|
+
|
|
320
|
+
```python
|
|
321
|
+
from bas.curl_parser import parse_curl, print_curl_summary
|
|
322
|
+
|
|
323
|
+
# See what's in a curl command
|
|
324
|
+
print_curl_summary(r'''curl "https://example.com" -H "User-Agent: ..." -b "cookie=value"''')
|
|
325
|
+
|
|
326
|
+
# Or get it as a dict
|
|
327
|
+
parsed = parse_curl(r'''curl "https://example.com" -H "User-Agent: ..." -b "cookie=value"''')
|
|
328
|
+
print(parsed["method"]) # "GET"
|
|
329
|
+
print(parsed["url"]) # "https://example.com"
|
|
330
|
+
print(parsed["headers"]) # {"User-Agent": "..."}
|
|
331
|
+
print(parsed["cookies"]) # {"cookie": "value"}
|
|
332
|
+
print(parsed["user_agent"]) # "Mozilla/5.0 ..."
|
|
333
|
+
print(parsed["referer"]) # "..."
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## SSL Verification
|
|
337
|
+
|
|
338
|
+
```python
|
|
339
|
+
# Disable SSL verification (default is enabled)
|
|
340
|
+
s = bas.Session(verify=False)
|
|
341
|
+
|
|
342
|
+
# Or per-request
|
|
343
|
+
r = s.get("https://self-signed.example.com", verify=False)
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## Timeouts
|
|
347
|
+
|
|
348
|
+
```python
|
|
349
|
+
# Set default timeout (seconds)
|
|
350
|
+
s = bas.Session(timeout=60)
|
|
351
|
+
|
|
352
|
+
# Or per-request
|
|
353
|
+
r = s.get("https://slow.example.com", timeout=120)
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
## Redirects
|
|
357
|
+
|
|
358
|
+
```python
|
|
359
|
+
# Follow redirects (default: True)
|
|
360
|
+
r = s.get("https://example.com/page")
|
|
361
|
+
|
|
362
|
+
# Don't follow redirects
|
|
363
|
+
r = s.get("https://example.com/page", allow_redirects=False)
|
|
364
|
+
print(r.status_code) # 302
|
|
365
|
+
print(r.location) # "https://example.com/dashboard"
|
|
366
|
+
|
|
367
|
+
# Set max redirects
|
|
368
|
+
s = bas.Session(max_redirects=5)
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
## Proxy Support
|
|
372
|
+
|
|
373
|
+
```python
|
|
374
|
+
# TODO: Proxy support coming in v1.1
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
## Real-World Example: Web Scraping
|
|
378
|
+
|
|
379
|
+
```python
|
|
380
|
+
import bas
|
|
381
|
+
|
|
382
|
+
# Step 1: Copy curl from DevTools
|
|
383
|
+
s = bas.from_curl(r'''curl "https://spaceshooter.net/faucet/ltc" ^
|
|
384
|
+
-H "accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" ^
|
|
385
|
+
-H "accept-language: en-US,en;q=0.9" ^
|
|
386
|
+
-H "sec-ch-ua: \"Chromium\";v=\"137\", \"Not/A)Brand\";v=\"24\"" ^
|
|
387
|
+
-H "sec-ch-ua-mobile: ?0" ^
|
|
388
|
+
-H "sec-ch-ua-platform: \"Windows\"" ^
|
|
389
|
+
-H "sec-fetch-dest: document" ^
|
|
390
|
+
-H "sec-fetch-mode: navigate" ^
|
|
391
|
+
-H "sec-fetch-site: none" ^
|
|
392
|
+
-H "upgrade-insecure-requests: 1" ^
|
|
393
|
+
-H "user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36" ^
|
|
394
|
+
-b "captcha=rscaptcha; cf_clearance=abc123; ci_session=xyz123; uf=def456"''')
|
|
395
|
+
|
|
396
|
+
# Step 2: Make the request
|
|
397
|
+
r = s.get("https://spaceshooter.net/faucet/ltc")
|
|
398
|
+
|
|
399
|
+
# Step 3: Parse the response
|
|
400
|
+
if r.ok:
|
|
401
|
+
print("Success!")
|
|
402
|
+
print(f"Status: {r.status_code}")
|
|
403
|
+
print(f"Content length: {len(r.text)}")
|
|
404
|
+
print(f"Cookies after request: {s.get_cookies('https://spaceshooter.net')}")
|
|
405
|
+
else:
|
|
406
|
+
print(f"Failed: {r.status_code}")
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
## Real-World Example: Form Submission
|
|
410
|
+
|
|
411
|
+
```python
|
|
412
|
+
import bas
|
|
413
|
+
|
|
414
|
+
# Copy the POST request curl from DevTools
|
|
415
|
+
s = bas.from_curl(r'''curl "https://example.com/login" ^
|
|
416
|
+
-H "content-type: application/x-www-form-urlencoded" ^
|
|
417
|
+
-H "user-agent: Mozilla/5.0 ..." ^
|
|
418
|
+
-H "referer: https://example.com/login" ^
|
|
419
|
+
-b "csrf_token=abc123" ^
|
|
420
|
+
--data-raw "username=myuser&password=mypass"''')
|
|
421
|
+
|
|
422
|
+
r = s.post("https://example.com/login", data={
|
|
423
|
+
"username": "myuser",
|
|
424
|
+
"password": "mypass",
|
|
425
|
+
})
|
|
426
|
+
|
|
427
|
+
print(r.status_code)
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
## Comparison with bas
|
|
431
|
+
|
|
432
|
+
### bas (BROKEN)
|
|
433
|
+
|
|
434
|
+
```python
|
|
435
|
+
from curl_cffi.requests import Session
|
|
436
|
+
|
|
437
|
+
s = Session(impersonate="chrome131")
|
|
438
|
+
|
|
439
|
+
# Cookies lost during redirects!
|
|
440
|
+
r1 = s.get("https://example.com/page") # 302 redirect
|
|
441
|
+
# r1 is missing cookies from Set-Cookie headers!
|
|
442
|
+
|
|
443
|
+
# Cookies not accumulating!
|
|
444
|
+
r2 = s.get("https://example.com/page")
|
|
445
|
+
# r2 has LOST cookies from r1!
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### bas (FIXED)
|
|
449
|
+
|
|
450
|
+
```python
|
|
451
|
+
import bas
|
|
452
|
+
|
|
453
|
+
s = bas.from_curl(r'''curl "https://example.com" -b "session=abc"''')
|
|
454
|
+
|
|
455
|
+
# Cookies survive redirects!
|
|
456
|
+
r1 = s.get("https://example.com/page") # 302 redirect
|
|
457
|
+
# ALL cookies preserved through redirect
|
|
458
|
+
|
|
459
|
+
# Cookies accumulate!
|
|
460
|
+
r2 = s.get("https://example.com/dashboard")
|
|
461
|
+
# r2 has ALL cookies from r1 + new ones
|
|
462
|
+
|
|
463
|
+
# No manual Cookie header needed!
|
|
464
|
+
# bas auto-injects cookies from the jar
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
## API Reference
|
|
468
|
+
|
|
469
|
+
### `bas.from_curl(curl_cmd, **kwargs)` → Session
|
|
470
|
+
|
|
471
|
+
Create a Session from a curl command. Main entry point.
|
|
472
|
+
|
|
473
|
+
### `bas.from_headers(url, headers, cookies, **kwargs)` → Session
|
|
474
|
+
|
|
475
|
+
Create a Session from raw headers and cookies.
|
|
476
|
+
|
|
477
|
+
### `bas.Session(headers, cookies, verify, timeout, allow_redirects, max_redirects)`
|
|
478
|
+
|
|
479
|
+
Pure Python HTTP session. Zero external dependencies.
|
|
480
|
+
|
|
481
|
+
**Methods:**
|
|
482
|
+
- `get(url, **kwargs)` → Response
|
|
483
|
+
- `post(url, **kwargs)` → Response
|
|
484
|
+
- `put(url, **kwargs)` → Response
|
|
485
|
+
- `delete(url, **kwargs)` → Response
|
|
486
|
+
- `patch(url, **kwargs)` → Response
|
|
487
|
+
- `head(url, **kwargs)` → Response
|
|
488
|
+
- `set_cookie(name, value, domain, path)` → None
|
|
489
|
+
- `get_cookies(url)` → dict
|
|
490
|
+
- `clear_cookies()` → None
|
|
491
|
+
- `save_cookies(filepath, format)` → None
|
|
492
|
+
- `load_cookies(filepath, format)` → None
|
|
493
|
+
|
|
494
|
+
### `bas.Cookie(name, value, domain, path, expires, max_age, secure, http_only, same_site)`
|
|
495
|
+
|
|
496
|
+
Individual cookie object with RFC 6265 compliance.
|
|
497
|
+
|
|
498
|
+
### `bas.CookieJar`
|
|
499
|
+
|
|
500
|
+
Thread-safe cookie container. Full RFC 6265 domain/path matching.
|
|
501
|
+
|
|
502
|
+
**Methods:**
|
|
503
|
+
- `add(cookie)` → None
|
|
504
|
+
- `remove(cookie)` → None
|
|
505
|
+
- `get(name, domain, path)` → Cookie | None
|
|
506
|
+
- `match(url)` → list[Cookie]
|
|
507
|
+
- `to_header(url)` → str
|
|
508
|
+
- `parse_set_cookie(header, url)` → Cookie
|
|
509
|
+
- `save_json(filepath)` → None
|
|
510
|
+
- `load_json(filepath)` → None
|
|
511
|
+
- `save_netscape(filepath)` → None
|
|
512
|
+
- `load_netscape(filepath)` → None
|
|
513
|
+
|
|
514
|
+
### `bas.Response`
|
|
515
|
+
|
|
516
|
+
HTTP response object.
|
|
517
|
+
|
|
518
|
+
**Properties:**
|
|
519
|
+
- `status_code` (int): HTTP status code
|
|
520
|
+
- `ok` (bool): True if status < 400
|
|
521
|
+
- `text` (str): Decoded text content
|
|
522
|
+
- `body` (bytes): Raw bytes
|
|
523
|
+
- `json` (Any): Parsed JSON
|
|
524
|
+
- `headers` (Headers): Response headers
|
|
525
|
+
- `url` (str): Final URL
|
|
526
|
+
- `elapsed` (float): Response time
|
|
527
|
+
- `history` (list): Redirect history
|
|
528
|
+
- `cookies` (CookieJar): Cookie jar
|
|
529
|
+
|
|
530
|
+
## License
|
|
531
|
+
|
|
532
|
+
MIT
|
|
@@ -6,7 +6,7 @@ No impersonation needed. No fingerprint chasing. No broken cookies. Just grab yo
|
|
|
6
6
|
|
|
7
7
|
## Why bas?
|
|
8
8
|
|
|
9
|
-
| Problem |
|
|
9
|
+
| Problem | bas | bas |
|
|
10
10
|
|---------|----------|-----|
|
|
11
11
|
| Cookies lost during redirects | **BUG** | **Fixed** |
|
|
12
12
|
| Cookies not accumulating across requests | **BUG** | **Fixed** |
|
|
@@ -265,7 +265,7 @@ r = s.get("https://example.com/page")
|
|
|
265
265
|
# Set-Cookie: tracking=xyz; Path=/
|
|
266
266
|
|
|
267
267
|
# ALL cookies are preserved through redirects
|
|
268
|
-
# No cookies lost (unlike
|
|
268
|
+
# No cookies lost (unlike bas!)
|
|
269
269
|
```
|
|
270
270
|
|
|
271
271
|
## Session Persistence
|
|
@@ -402,9 +402,9 @@ r = s.post("https://example.com/login", data={
|
|
|
402
402
|
print(r.status_code)
|
|
403
403
|
```
|
|
404
404
|
|
|
405
|
-
## Comparison with
|
|
405
|
+
## Comparison with bas
|
|
406
406
|
|
|
407
|
-
###
|
|
407
|
+
### bas (BROKEN)
|
|
408
408
|
|
|
409
409
|
```python
|
|
410
410
|
from curl_cffi.requests import Session
|
|
@@ -28,7 +28,7 @@ from .curl_parser import parse_curl, curl_to_session, print_curl_summary
|
|
|
28
28
|
from .cookies import Cookie, CookieJar
|
|
29
29
|
from .models import Headers, PreparedRequest, Response, HTTPError
|
|
30
30
|
|
|
31
|
-
__version__ = "1.0.
|
|
31
|
+
__version__ = "1.0.3"
|
|
32
32
|
__author__ = "bas"
|
|
33
33
|
|
|
34
34
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Low-level ctypes bindings for libcurl with curl-impersonate support.
|
|
3
3
|
|
|
4
4
|
This module provides Python bindings to libcurl (or curl-impersonate if available)
|
|
5
|
-
using ctypes. It avoids cffi compilation issues that plague
|
|
5
|
+
using ctypes. It avoids cffi compilation issues that plague bas.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import ctypes
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
RFC 6265 compliant CookieJar.
|
|
3
3
|
|
|
4
|
-
This module fixes the major cookie issues in
|
|
4
|
+
This module fixes the major cookie issues in bas:
|
|
5
5
|
1. Cookies lost during redirects (#55)
|
|
6
6
|
2. Cookies not accumulating across session requests (#582)
|
|
7
7
|
3. Need for manual cookie header management
|
|
@@ -226,7 +226,7 @@ class CookieJar:
|
|
|
226
226
|
"""
|
|
227
227
|
Thread-safe, RFC 6265 compliant cookie jar.
|
|
228
228
|
|
|
229
|
-
This is the heart of bas's cookie handling. Unlike
|
|
229
|
+
This is the heart of bas's cookie handling. Unlike bas's
|
|
230
230
|
approach (which relies on libcurl's internal cookie engine and loses
|
|
231
231
|
cookies during redirects), bas manages cookies entirely in Python.
|
|
232
232
|
|
|
@@ -5,7 +5,7 @@ This module provides browser fingerprint profiles that override HTTP/2
|
|
|
5
5
|
settings and headers to mimic real browsers. When curl-impersonate is
|
|
6
6
|
available, it also enables TLS fingerprint mimicry.
|
|
7
7
|
|
|
8
|
-
Unlike
|
|
8
|
+
Unlike bas which bundles impersonation in a single enum, bas
|
|
9
9
|
separates the concerns:
|
|
10
10
|
- TLS impersonation (requires curl-impersonate library)
|
|
11
11
|
- HTTP/2 fingerprinting (works with standard libcurl)
|
|
@@ -348,7 +348,7 @@ PROFILES: dict[str, BrowserProfile] = {
|
|
|
348
348
|
"edge131": EDGE131,
|
|
349
349
|
}
|
|
350
350
|
|
|
351
|
-
# Aliases for
|
|
351
|
+
# Aliases for bas compatibility
|
|
352
352
|
CHROME_ALIASES = {
|
|
353
353
|
"chrome": CHROME131,
|
|
354
354
|
"chrome99": CHROME131, # closest available
|
|
@@ -385,7 +385,7 @@ def get_profile(name: str) -> BrowserProfile:
|
|
|
385
385
|
"""
|
|
386
386
|
Get a browser profile by name.
|
|
387
387
|
|
|
388
|
-
Supports both exact names and
|
|
388
|
+
Supports both exact names and bas-compatible aliases.
|
|
389
389
|
|
|
390
390
|
Args:
|
|
391
391
|
name: Profile name (e.g., "chrome131", "firefox120", "chrome")
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Session-based HTTP client with proper cookie management.
|
|
3
3
|
|
|
4
4
|
This is the main public API of bas. It provides a requests-like
|
|
5
|
-
interface while fixing ALL of
|
|
5
|
+
interface while fixing ALL of bas's cookie bugs:
|
|
6
6
|
|
|
7
7
|
Fix #1: Cookies accumulate correctly across requests in a session
|
|
8
8
|
Fix #2: Cookies are inherited during redirects (even cross-domain)
|
|
@@ -92,7 +92,7 @@ class Session:
|
|
|
92
92
|
"""
|
|
93
93
|
A persistent HTTP session with automatic cookie management.
|
|
94
94
|
|
|
95
|
-
Key improvements over
|
|
95
|
+
Key improvements over bas.Session:
|
|
96
96
|
- Cookies ALWAYS accumulate across requests (fixes #582)
|
|
97
97
|
- Cookies are inherited during redirects (fixes #55)
|
|
98
98
|
- Automatic Cookie header injection (no manual construction)
|
|
@@ -358,7 +358,7 @@ class Session:
|
|
|
358
358
|
)
|
|
359
359
|
|
|
360
360
|
# ── CRITICAL: Parse Set-Cookie and update our jar ──
|
|
361
|
-
# This is what
|
|
361
|
+
# This is what bas gets wrong — it relies on curl's internal
|
|
362
362
|
# cookie engine which loses cookies during redirects.
|
|
363
363
|
self._cookie_jar.parse_set_cookie_headers(
|
|
364
364
|
response_headers,
|
|
@@ -485,7 +485,7 @@ class Session:
|
|
|
485
485
|
3. Re-apply cookies for the next request via our jar
|
|
486
486
|
4. Never lose a single cookie
|
|
487
487
|
|
|
488
|
-
This fixes
|
|
488
|
+
This fixes bas issue #55 where cookies were lost during redirects.
|
|
489
489
|
"""
|
|
490
490
|
if history is None:
|
|
491
491
|
history = []
|
|
@@ -508,7 +508,7 @@ class Session:
|
|
|
508
508
|
redirect_url = urljoin(response.url, location)
|
|
509
509
|
|
|
510
510
|
# ── COOKIE FIX: Parse Set-Cookie before redirect ──
|
|
511
|
-
#
|
|
511
|
+
# bas loses cookies here because it switches to curl's
|
|
512
512
|
# internal cookie engine for the redirect. We don't.
|
|
513
513
|
# (Already done in _curl_perform above)
|
|
514
514
|
|