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