curlify3 0.8__tar.gz → 0.9__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.
- {curlify3-0.8 → curlify3-0.9}/PKG-INFO +31 -5
- {curlify3-0.8 → curlify3-0.9}/README.md +30 -4
- {curlify3-0.8 → curlify3-0.9}/curlify3/__init__.py +1 -1
- {curlify3-0.8 → curlify3-0.9}/curlify3/_curl.py +59 -31
- {curlify3-0.8 → curlify3-0.9}/pyproject.toml +1 -1
- {curlify3-0.8 → curlify3-0.9}/.gitignore +0 -0
- {curlify3-0.8 → curlify3-0.9}/curlify3/_base.py +0 -0
- {curlify3-0.8 → curlify3-0.9}/curlify3/_req_aiohttp.py +0 -0
- {curlify3-0.8 → curlify3-0.9}/curlify3/_req_httpx.py +0 -0
- {curlify3-0.8 → curlify3-0.9}/curlify3/_req_httpx2.py +0 -0
- {curlify3-0.8 → curlify3-0.9}/curlify3/_req_requests.py +0 -0
- {curlify3-0.8 → curlify3-0.9}/curlify3/_req_starlette.py +0 -0
- {curlify3-0.8 → curlify3-0.9}/curlify3/_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: curlify3
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9
|
|
4
4
|
Summary: yet another library to convert request object to curl command
|
|
5
5
|
Project-URL: Repository, https://github.com/shpaker/curlify3
|
|
6
6
|
Author-email: "A.Shpak" <shpaker@gmail.com>
|
|
@@ -25,6 +25,7 @@ Convert request objects from popular Python HTTP libraries into ready-to-run `cu
|
|
|
25
25
|
- Faithful rendering of headers, query parameters, cookies (`-b`), and bodies
|
|
26
26
|
- Body payloads: text, JSON, form-encoded, multipart, binary
|
|
27
27
|
- POSIX shell output by default, Windows PowerShell output with `shell="powershell"`
|
|
28
|
+
- One-line output by default, multi-line with `pretty=True` and long option names with `long_options=True`
|
|
28
29
|
- Zero runtime dependencies
|
|
29
30
|
|
|
30
31
|
## Installation
|
|
@@ -117,6 +118,31 @@ print(to_curl(req))
|
|
|
117
118
|
|
|
118
119
|
`to_curl_async()` works with `httpx2.Request` too.
|
|
119
120
|
|
|
121
|
+
### Readable output
|
|
122
|
+
|
|
123
|
+
`pretty=True` puts every option on its own line, and `long_options=True` spells the options out (`--header` instead of `-H`). They are independent, so either can be used alone.
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
import requests
|
|
127
|
+
from curlify3 import to_curl
|
|
128
|
+
|
|
129
|
+
req = requests.Request(
|
|
130
|
+
"POST",
|
|
131
|
+
"https://httpbin.org/post",
|
|
132
|
+
json={"date": "2026-08-10"},
|
|
133
|
+
).prepare()
|
|
134
|
+
|
|
135
|
+
print(to_curl(req, pretty=True, long_options=True))
|
|
136
|
+
# curl https://httpbin.org/post \
|
|
137
|
+
# --request POST \
|
|
138
|
+
# --header 'content-type: application/json' \
|
|
139
|
+
# --data '{"date": "2026-08-10"}'
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The url moves to the first line, where `curl` reads it just as well as in the trailing position — the same layout Chrome DevTools' "Copy as cURL" produces. A request with no options stays on one line.
|
|
143
|
+
|
|
144
|
+
`pretty=True` is rejected with a `ValueError` for `shell="powershell"`: the `--%` token that dialect relies on is effective only until the next newline, and a backtick cannot extend it, so a multi-line command would be passed to `curl.exe` in pieces.
|
|
145
|
+
|
|
120
146
|
### Windows PowerShell
|
|
121
147
|
|
|
122
148
|
By default the command is formatted for POSIX shells. Pass `shell="powershell"` to get one that pastes into Windows PowerShell 5.1.
|
|
@@ -172,17 +198,17 @@ async def echo(request: Request):
|
|
|
172
198
|
|
|
173
199
|
## API
|
|
174
200
|
|
|
175
|
-
### `to_curl(request, shell="sh") -> str`
|
|
201
|
+
### `to_curl(request, shell="sh", pretty=False, long_options=False) -> str`
|
|
176
202
|
|
|
177
203
|
Render a request object as a `curl` command. Use for synchronous request types (`requests.PreparedRequest`, `httpx.Request`, `httpx2.Request`).
|
|
178
204
|
|
|
179
|
-
### `to_curl_async(request, shell="sh") -> str`
|
|
205
|
+
### `to_curl_async(request, shell="sh", pretty=False, long_options=False) -> str`
|
|
180
206
|
|
|
181
207
|
Async variant. Use for server-side request objects whose body must be `await`-ed (`aiohttp.web.Request`, `starlette.requests.Request`) or when you prefer the async pathway for `httpx` / `httpx2`.
|
|
182
208
|
|
|
183
|
-
`shell` selects the output dialect: `"sh"` (default, POSIX shells) or `"powershell"` (Windows PowerShell 5.1; for `pwsh` 7.2+ see the PowerShell section).
|
|
209
|
+
`shell` selects the output dialect: `"sh"` (default, POSIX shells) or `"powershell"` (Windows PowerShell 5.1; for `pwsh` 7.2+ see the PowerShell section). `pretty` breaks the command across lines, `long_options` spells the options out; both default to `False`, which keeps the output on a single line with short options.
|
|
184
210
|
|
|
185
|
-
Both functions raise `ValueError` if the request type or the `shell` value is not recognized
|
|
211
|
+
Both functions raise `ValueError` if the request type or the `shell` value is not recognized, or if `pretty=True` is combined with `shell="powershell"`.
|
|
186
212
|
|
|
187
213
|
## Supported request objects
|
|
188
214
|
|
|
@@ -16,6 +16,7 @@ Convert request objects from popular Python HTTP libraries into ready-to-run `cu
|
|
|
16
16
|
- Faithful rendering of headers, query parameters, cookies (`-b`), and bodies
|
|
17
17
|
- Body payloads: text, JSON, form-encoded, multipart, binary
|
|
18
18
|
- POSIX shell output by default, Windows PowerShell output with `shell="powershell"`
|
|
19
|
+
- One-line output by default, multi-line with `pretty=True` and long option names with `long_options=True`
|
|
19
20
|
- Zero runtime dependencies
|
|
20
21
|
|
|
21
22
|
## Installation
|
|
@@ -108,6 +109,31 @@ print(to_curl(req))
|
|
|
108
109
|
|
|
109
110
|
`to_curl_async()` works with `httpx2.Request` too.
|
|
110
111
|
|
|
112
|
+
### Readable output
|
|
113
|
+
|
|
114
|
+
`pretty=True` puts every option on its own line, and `long_options=True` spells the options out (`--header` instead of `-H`). They are independent, so either can be used alone.
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
import requests
|
|
118
|
+
from curlify3 import to_curl
|
|
119
|
+
|
|
120
|
+
req = requests.Request(
|
|
121
|
+
"POST",
|
|
122
|
+
"https://httpbin.org/post",
|
|
123
|
+
json={"date": "2026-08-10"},
|
|
124
|
+
).prepare()
|
|
125
|
+
|
|
126
|
+
print(to_curl(req, pretty=True, long_options=True))
|
|
127
|
+
# curl https://httpbin.org/post \
|
|
128
|
+
# --request POST \
|
|
129
|
+
# --header 'content-type: application/json' \
|
|
130
|
+
# --data '{"date": "2026-08-10"}'
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The url moves to the first line, where `curl` reads it just as well as in the trailing position — the same layout Chrome DevTools' "Copy as cURL" produces. A request with no options stays on one line.
|
|
134
|
+
|
|
135
|
+
`pretty=True` is rejected with a `ValueError` for `shell="powershell"`: the `--%` token that dialect relies on is effective only until the next newline, and a backtick cannot extend it, so a multi-line command would be passed to `curl.exe` in pieces.
|
|
136
|
+
|
|
111
137
|
### Windows PowerShell
|
|
112
138
|
|
|
113
139
|
By default the command is formatted for POSIX shells. Pass `shell="powershell"` to get one that pastes into Windows PowerShell 5.1.
|
|
@@ -163,17 +189,17 @@ async def echo(request: Request):
|
|
|
163
189
|
|
|
164
190
|
## API
|
|
165
191
|
|
|
166
|
-
### `to_curl(request, shell="sh") -> str`
|
|
192
|
+
### `to_curl(request, shell="sh", pretty=False, long_options=False) -> str`
|
|
167
193
|
|
|
168
194
|
Render a request object as a `curl` command. Use for synchronous request types (`requests.PreparedRequest`, `httpx.Request`, `httpx2.Request`).
|
|
169
195
|
|
|
170
|
-
### `to_curl_async(request, shell="sh") -> str`
|
|
196
|
+
### `to_curl_async(request, shell="sh", pretty=False, long_options=False) -> str`
|
|
171
197
|
|
|
172
198
|
Async variant. Use for server-side request objects whose body must be `await`-ed (`aiohttp.web.Request`, `starlette.requests.Request`) or when you prefer the async pathway for `httpx` / `httpx2`.
|
|
173
199
|
|
|
174
|
-
`shell` selects the output dialect: `"sh"` (default, POSIX shells) or `"powershell"` (Windows PowerShell 5.1; for `pwsh` 7.2+ see the PowerShell section).
|
|
200
|
+
`shell` selects the output dialect: `"sh"` (default, POSIX shells) or `"powershell"` (Windows PowerShell 5.1; for `pwsh` 7.2+ see the PowerShell section). `pretty` breaks the command across lines, `long_options` spells the options out; both default to `False`, which keeps the output on a single line with short options.
|
|
175
201
|
|
|
176
|
-
Both functions raise `ValueError` if the request type or the `shell` value is not recognized
|
|
202
|
+
Both functions raise `ValueError` if the request type or the `shell` value is not recognized, or if `pretty=True` is combined with `shell="powershell"`.
|
|
177
203
|
|
|
178
204
|
## Supported request objects
|
|
179
205
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import re
|
|
2
2
|
|
|
3
3
|
from asyncio import iscoroutine
|
|
4
|
-
from typing import Callable, NamedTuple
|
|
4
|
+
from typing import Callable, NamedTuple, Optional
|
|
5
5
|
|
|
6
6
|
from curlify3._utils import make_request_obj, make_request_obj_async
|
|
7
7
|
|
|
@@ -14,6 +14,21 @@ PS_TRAILING_BACKSLASHES = re.compile(r"\\+$")
|
|
|
14
14
|
SH = "sh"
|
|
15
15
|
POWERSHELL = "powershell"
|
|
16
16
|
|
|
17
|
+
SHORT_OPTIONS = {
|
|
18
|
+
"request": "-X",
|
|
19
|
+
"header": "-H",
|
|
20
|
+
"cookie": "-b",
|
|
21
|
+
"data": "-d",
|
|
22
|
+
"form": "-F",
|
|
23
|
+
}
|
|
24
|
+
LONG_OPTIONS = {
|
|
25
|
+
"request": "--request",
|
|
26
|
+
"header": "--header",
|
|
27
|
+
"cookie": "--cookie",
|
|
28
|
+
"data": "--data",
|
|
29
|
+
"form": "--form",
|
|
30
|
+
}
|
|
31
|
+
|
|
17
32
|
|
|
18
33
|
def quote_sh(value):
|
|
19
34
|
return f"'{value}'"
|
|
@@ -42,10 +57,12 @@ class ShellConfig(NamedTuple):
|
|
|
42
57
|
quote: Callable
|
|
43
58
|
quote_url: Callable
|
|
44
59
|
quote_cookies: Callable
|
|
60
|
+
# what separates arguments in pretty mode, None when the shell cannot span lines
|
|
61
|
+
pretty_separator: Optional[str]
|
|
45
62
|
|
|
46
63
|
|
|
47
64
|
SHELLS = {
|
|
48
|
-
SH: ShellConfig("curl", "", quote_sh, quote_sh_url, quote_sh_cookies),
|
|
65
|
+
SH: ShellConfig("curl", "", quote_sh, quote_sh_url, quote_sh_cookies, " \\\n "),
|
|
49
66
|
# --% is the stop-parsing token: Windows PowerShell 5.1 (the dialect's target) hands
|
|
50
67
|
# everything after it to curl.exe verbatim (only %VAR% references expand), leaving the
|
|
51
68
|
# C runtime as the single parser — 5.1's own argument binder re-quotes by counting every
|
|
@@ -53,65 +70,72 @@ SHELLS = {
|
|
|
53
70
|
# pwsh 7.2+ binds even these arguments the new way and needs
|
|
54
71
|
# $PSNativeCommandArgumentPassing = 'Legacy' in the session first (documented in README);
|
|
55
72
|
# the url and cookies are always quoted to keep whitespace safe for the C runtime parser
|
|
56
|
-
|
|
73
|
+
# pretty is impossible here: --% is effective only until the next newline and the line
|
|
74
|
+
# continuation character (`) cannot extend it (about_Parsing), so a multi-line command
|
|
75
|
+
# would pass the backtick to curl.exe and run the next line on its own
|
|
76
|
+
POWERSHELL: ShellConfig("curl.exe", "--%", quote_powershell, quote_powershell, quote_powershell, None),
|
|
57
77
|
}
|
|
58
78
|
|
|
59
79
|
|
|
60
|
-
def make_curl_headers(headers, quote):
|
|
61
|
-
|
|
62
|
-
for header, value in headers.items()
|
|
63
|
-
results.append(f"-H {quote(f'{header}: {value}')}")
|
|
64
|
-
return " ".join(results)
|
|
80
|
+
def make_curl_headers(headers, quote, options):
|
|
81
|
+
option = options["header"]
|
|
82
|
+
return [f"{option} {quote(f'{header}: {value}')}" for header, value in headers.items()]
|
|
65
83
|
|
|
66
84
|
|
|
67
|
-
def make_curl_cookies(cookies, quote_cookies):
|
|
85
|
+
def make_curl_cookies(cookies, quote_cookies, options):
|
|
68
86
|
if not cookies:
|
|
69
|
-
return
|
|
70
|
-
return f"
|
|
87
|
+
return []
|
|
88
|
+
return [f"{options['cookie']} {quote_cookies(cookies)}"]
|
|
71
89
|
|
|
72
90
|
|
|
73
|
-
def make_multipart_curl_args(body, quote):
|
|
91
|
+
def make_multipart_curl_args(body, quote, options):
|
|
92
|
+
option = options["form"]
|
|
74
93
|
body_parts = []
|
|
75
94
|
body = body.encode() if isinstance(body, str) else body
|
|
76
95
|
for matched in MULTIPART_FORM_DATA.finditer(body):
|
|
77
96
|
groups = matched.groups()
|
|
78
|
-
body_parts.append(f"
|
|
97
|
+
body_parts.append(f"{option} {quote(f'{groups[0].decode()}={groups[1].decode()}')}")
|
|
79
98
|
for matched in MULTIPART_FILE_DATA.finditer(body):
|
|
80
99
|
groups = matched.groups()
|
|
81
|
-
body_parts.append(f"
|
|
82
|
-
return
|
|
100
|
+
body_parts.append(f"{option} {quote(f'{groups[0].decode()}=@{groups[1].decode()}')}")
|
|
101
|
+
return body_parts
|
|
83
102
|
|
|
84
103
|
|
|
85
|
-
def make_curl_body(body, headers, quote):
|
|
104
|
+
def make_curl_body(body, headers, quote, options):
|
|
86
105
|
if "multipart" in headers.get("content-type", ""):
|
|
87
|
-
return make_multipart_curl_args(body, quote)
|
|
106
|
+
return make_multipart_curl_args(body, quote, options)
|
|
88
107
|
if not body:
|
|
89
|
-
return
|
|
90
|
-
return f"
|
|
108
|
+
return []
|
|
109
|
+
return [f"{options['data']} {quote(body)}"]
|
|
91
110
|
|
|
92
111
|
|
|
93
|
-
def make_curl_string(method, url, headers, body, cookies, http2=False, shell=SH):
|
|
112
|
+
def make_curl_string(method, url, headers, body, cookies, http2=False, shell=SH, pretty=False, long_options=False):
|
|
94
113
|
if shell not in SHELLS:
|
|
95
114
|
raise ValueError(f"unknown shell: {shell!r}, expected one of {sorted(SHELLS)}")
|
|
96
115
|
shell_conf = SHELLS[shell]
|
|
116
|
+
if pretty and shell_conf.pretty_separator is None:
|
|
117
|
+
raise ValueError(f"pretty output is not supported for shell: {shell!r}")
|
|
118
|
+
options = LONG_OPTIONS if long_options else SHORT_OPTIONS
|
|
97
119
|
if "content-length" in headers:
|
|
98
120
|
del headers["content-length"]
|
|
99
121
|
if body and isinstance(body, (str, bytes)) and not headers.get("content-type"):
|
|
100
122
|
headers["content-type"] = "plain/text"
|
|
101
|
-
|
|
102
|
-
shell_conf.binary,
|
|
103
|
-
shell_conf.args_prefix,
|
|
123
|
+
args = [
|
|
104
124
|
"--http2" if http2 else None,
|
|
105
|
-
f"
|
|
106
|
-
make_curl_cookies(cookies, shell_conf.quote_cookies),
|
|
107
|
-
make_curl_headers(headers, shell_conf.quote),
|
|
108
|
-
make_curl_body(body, headers, shell_conf.quote),
|
|
109
|
-
shell_conf.quote_url(url),
|
|
125
|
+
f"{options['request']} {method}" if method != "GET" else None,
|
|
126
|
+
*make_curl_cookies(cookies, shell_conf.quote_cookies, options),
|
|
127
|
+
*make_curl_headers(headers, shell_conf.quote, options),
|
|
128
|
+
*make_curl_body(body, headers, shell_conf.quote, options),
|
|
110
129
|
]
|
|
111
|
-
|
|
130
|
+
args = [str(entity) for entity in args if entity]
|
|
131
|
+
command = " ".join([part for part in (shell_conf.binary, shell_conf.args_prefix) if part])
|
|
132
|
+
url = shell_conf.quote_url(url)
|
|
133
|
+
if pretty:
|
|
134
|
+
return shell_conf.pretty_separator.join([f"{command} {url}", *args])
|
|
135
|
+
return " ".join([command, *args, url])
|
|
112
136
|
|
|
113
137
|
|
|
114
|
-
def to_curl(request, shell=SH):
|
|
138
|
+
def to_curl(request, shell=SH, pretty=False, long_options=False):
|
|
115
139
|
data = make_request_obj(request)
|
|
116
140
|
return make_curl_string(
|
|
117
141
|
method=data.method,
|
|
@@ -121,10 +145,12 @@ def to_curl(request, shell=SH):
|
|
|
121
145
|
cookies=data.cookies,
|
|
122
146
|
http2=getattr(data, "http2", False),
|
|
123
147
|
shell=shell,
|
|
148
|
+
pretty=pretty,
|
|
149
|
+
long_options=long_options,
|
|
124
150
|
)
|
|
125
151
|
|
|
126
152
|
|
|
127
|
-
async def to_curl_async(request, shell=SH):
|
|
153
|
+
async def to_curl_async(request, shell=SH, pretty=False, long_options=False):
|
|
128
154
|
data = make_request_obj_async(request)
|
|
129
155
|
body = data.body()
|
|
130
156
|
if iscoroutine(body):
|
|
@@ -137,4 +163,6 @@ async def to_curl_async(request, shell=SH):
|
|
|
137
163
|
cookies=data.cookies,
|
|
138
164
|
http2=getattr(data, "http2", False),
|
|
139
165
|
shell=shell,
|
|
166
|
+
pretty=pretty,
|
|
167
|
+
long_options=long_options,
|
|
140
168
|
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|