curlify3 0.1rc1__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.
- curlify3/__init__.py +7 -0
- curlify3/_base.py +40 -0
- curlify3/_curl.py +90 -0
- curlify3/_req_httpx.py +27 -0
- curlify3/_req_requests.py +16 -0
- curlify3/_req_starlette.py +20 -0
- curlify3/_utils.py +50 -0
- curlify3-0.1rc1.dist-info/METADATA +37 -0
- curlify3-0.1rc1.dist-info/RECORD +10 -0
- curlify3-0.1rc1.dist-info/WHEEL +4 -0
curlify3/__init__.py
ADDED
curlify3/_base.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class BaseRequestData(ABC):
|
|
5
|
+
|
|
6
|
+
def __init__(self, request) -> None:
|
|
7
|
+
if not isinstance(request, self._instance_of):
|
|
8
|
+
raise ValueError
|
|
9
|
+
self._request = request
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def url(self):
|
|
13
|
+
return str(self._request.url)
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
def method(self):
|
|
17
|
+
return self._request.method
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def headers(self):
|
|
21
|
+
headers = {name.lower(): value for name, value in dict(self._request.headers).items()}
|
|
22
|
+
if self._request.headers.get("cookie"):
|
|
23
|
+
del headers["cookie"]
|
|
24
|
+
return headers
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def cookies(self):
|
|
28
|
+
if "cookie" not in self._request.headers:
|
|
29
|
+
return None
|
|
30
|
+
return self._request.headers.get("cookie")
|
|
31
|
+
|
|
32
|
+
@abstractmethod
|
|
33
|
+
def body(self):
|
|
34
|
+
raise NotImplementedError
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class AsyncBaseRequestData(BaseRequestData, ABC):
|
|
38
|
+
@abstractmethod
|
|
39
|
+
async def body(self):
|
|
40
|
+
raise NotImplementedError
|
curlify3/_curl.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
from asyncio import iscoroutine
|
|
4
|
+
|
|
5
|
+
from curlify3._utils import make_request_obj, make_request_obj_async
|
|
6
|
+
|
|
7
|
+
MULTIPART_FORM_DATA = re.compile(rb'form-data; name="(.[^"]+)"\r\n\r\n(.+)\r\n')
|
|
8
|
+
MULTIPART_FILE_DATA = re.compile(rb'form-data; name="(.[^"]+)"; filename="(.[^"]+)"')
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def make_full_url(url) -> str:
|
|
12
|
+
return url if not "&" in url else f"'{url}'"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def make_curl_headers(headers):
|
|
16
|
+
results = []
|
|
17
|
+
for header, value in headers.items():
|
|
18
|
+
results.append(f"-H '{header}: {value}'")
|
|
19
|
+
return " ".join(results)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def make_curl_cookies(cookies):
|
|
23
|
+
if not cookies:
|
|
24
|
+
return None
|
|
25
|
+
if " " in cookies:
|
|
26
|
+
cookies = f"'{cookies}'"
|
|
27
|
+
return f"-b {cookies}"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def make_multipart_curl_args(body):
|
|
31
|
+
body_parts = []
|
|
32
|
+
for matched in MULTIPART_FORM_DATA.finditer(body):
|
|
33
|
+
groups = matched.groups()
|
|
34
|
+
body_parts.append(f"-F '{groups[0].decode()}={groups[1].decode()}'")
|
|
35
|
+
for matched in MULTIPART_FILE_DATA.finditer(body):
|
|
36
|
+
groups = matched.groups()
|
|
37
|
+
body_parts.append(f"-F '{groups[0].decode()}=@{groups[1].decode()}'")
|
|
38
|
+
return " ".join(body_parts)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def make_curl_body(
|
|
42
|
+
body,
|
|
43
|
+
headers,
|
|
44
|
+
):
|
|
45
|
+
if "multipart" in headers.get("content-type", ""):
|
|
46
|
+
return make_multipart_curl_args(body)
|
|
47
|
+
if not body:
|
|
48
|
+
return ""
|
|
49
|
+
return f"-d '{body}'"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def make_curl_string(method, url, headers, body, cookies):
|
|
53
|
+
if "content-length" in headers:
|
|
54
|
+
del headers["content-length"]
|
|
55
|
+
if body and isinstance(body, (str, bytes)) and not headers.get("content-type"):
|
|
56
|
+
headers["content-type"] = "plain/text"
|
|
57
|
+
cli_parts = [
|
|
58
|
+
f"curl",
|
|
59
|
+
f"-X {method}" if method != "GET" else None,
|
|
60
|
+
make_curl_cookies(cookies),
|
|
61
|
+
make_curl_headers(headers),
|
|
62
|
+
make_curl_body(body, headers),
|
|
63
|
+
make_full_url(url),
|
|
64
|
+
]
|
|
65
|
+
return " ".join([str(entity) for entity in cli_parts if entity])
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def to_curl(request):
|
|
69
|
+
data = make_request_obj(request)
|
|
70
|
+
return make_curl_string(
|
|
71
|
+
method=data.method,
|
|
72
|
+
url=data.url,
|
|
73
|
+
headers=data.headers,
|
|
74
|
+
body=data.body(),
|
|
75
|
+
cookies=data.cookies,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
async def to_curl_async(request):
|
|
80
|
+
data = make_request_obj_async(request)
|
|
81
|
+
body = data.body()
|
|
82
|
+
if iscoroutine(body):
|
|
83
|
+
body = await body
|
|
84
|
+
return make_curl_string(
|
|
85
|
+
method=data.method,
|
|
86
|
+
url=data.url,
|
|
87
|
+
headers=data.headers,
|
|
88
|
+
body=body,
|
|
89
|
+
cookies=data.cookies,
|
|
90
|
+
)
|
curlify3/_req_httpx.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
|
|
3
|
+
from curlify3._base import AsyncBaseRequestData, BaseRequestData
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class HttpxRequest(BaseRequestData):
|
|
7
|
+
_instance_of = httpx.Request
|
|
8
|
+
|
|
9
|
+
def body(self):
|
|
10
|
+
data = self._request.read()
|
|
11
|
+
try:
|
|
12
|
+
return data.decode()
|
|
13
|
+
except UnicodeDecodeError:
|
|
14
|
+
pass
|
|
15
|
+
return data
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AsyncHttpxRequest(AsyncBaseRequestData, HttpxRequest):
|
|
19
|
+
_instance_of = httpx.Request
|
|
20
|
+
|
|
21
|
+
async def body(self):
|
|
22
|
+
data = await self._request.aread()
|
|
23
|
+
try:
|
|
24
|
+
return data.decode()
|
|
25
|
+
except UnicodeDecodeError:
|
|
26
|
+
pass
|
|
27
|
+
return data
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
|
|
3
|
+
from curlify3._base import BaseRequestData
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class RequestsRequest(BaseRequestData):
|
|
7
|
+
_instance_of = requests.PreparedRequest
|
|
8
|
+
|
|
9
|
+
def body(self):
|
|
10
|
+
body = self._request.body
|
|
11
|
+
if isinstance(body, bytes):
|
|
12
|
+
try:
|
|
13
|
+
return body.decode()
|
|
14
|
+
except UnicodeDecodeError:
|
|
15
|
+
pass
|
|
16
|
+
return body
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from typing import Optional, Union
|
|
2
|
+
|
|
3
|
+
import starlette
|
|
4
|
+
|
|
5
|
+
from starlette.requests import Request
|
|
6
|
+
|
|
7
|
+
from curlify3._base import BaseRequestData
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class StarletteRequest(BaseRequestData):
|
|
11
|
+
_instance_of = starlette.requests.Request
|
|
12
|
+
|
|
13
|
+
async def body(self) -> Optional[Union[bytes, str]]:
|
|
14
|
+
self._request: starlette.requests.Request
|
|
15
|
+
data = await self._request.body()
|
|
16
|
+
try:
|
|
17
|
+
return data.decode()
|
|
18
|
+
except UnicodeDecodeError:
|
|
19
|
+
pass
|
|
20
|
+
return data
|
curlify3/_utils.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from contextlib import suppress
|
|
2
|
+
|
|
3
|
+
_REQUEST_DATA_CLASSES = []
|
|
4
|
+
_REQUEST_DATA_CLASSES_ASYNC = []
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
with suppress(ImportError):
|
|
8
|
+
from curlify3._req_requests import RequestsRequest
|
|
9
|
+
|
|
10
|
+
_REQUEST_DATA_CLASSES.append(RequestsRequest)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
with suppress(ImportError):
|
|
14
|
+
from curlify3._req_httpx import HttpxRequest
|
|
15
|
+
|
|
16
|
+
_REQUEST_DATA_CLASSES.append(HttpxRequest)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
with suppress(ImportError):
|
|
20
|
+
from curlify3._req_httpx import AsyncHttpxRequest
|
|
21
|
+
|
|
22
|
+
_REQUEST_DATA_CLASSES_ASYNC.append(AsyncHttpxRequest)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
with suppress(ImportError):
|
|
26
|
+
from curlify3._req_starlette import StarletteRequest
|
|
27
|
+
|
|
28
|
+
_REQUEST_DATA_CLASSES_ASYNC.append(StarletteRequest)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _find_request_data_obj(request, request_data_classes):
|
|
32
|
+
obj = None
|
|
33
|
+
for _cls in request_data_classes:
|
|
34
|
+
try:
|
|
35
|
+
obj = _cls(request)
|
|
36
|
+
except ValueError:
|
|
37
|
+
continue
|
|
38
|
+
if obj is None:
|
|
39
|
+
raise ValueError('unknown request object')
|
|
40
|
+
return obj
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def make_request_obj(request):
|
|
44
|
+
return _find_request_data_obj(request, _REQUEST_DATA_CLASSES)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def make_request_obj_async(request):
|
|
48
|
+
if data_obj := _find_request_data_obj(request, _REQUEST_DATA_CLASSES_ASYNC):
|
|
49
|
+
return data_obj
|
|
50
|
+
return _find_request_data_obj(request, _REQUEST_DATA_CLASSES)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: curlify3
|
|
3
|
+
Version: 0.1rc1
|
|
4
|
+
Summary: yet another python Request objects curlificator
|
|
5
|
+
Author: A.Shpak
|
|
6
|
+
Author-email: aleksander.shpak.als@gmail.com
|
|
7
|
+
Requires-Python: >=3.9,<4.0
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# yet another library to convert python requests request object to curl command
|
|
17
|
+
|
|
18
|
+
[](https://pypi.python.org/pypi/curlify3)
|
|
19
|
+
[](https://pypi.python.org/pypi/curlify3)
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
pip install curlify3
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Example
|
|
28
|
+
|
|
29
|
+
```py
|
|
30
|
+
from curlify3 import to_curl
|
|
31
|
+
import requests
|
|
32
|
+
|
|
33
|
+
response = requests.get("http://google.ru")
|
|
34
|
+
print(to_curl(response.request))
|
|
35
|
+
# curl -H 'user-agent: python-requests/2.32.3' -H 'accept-encoding: gzip, deflate' -H 'accept: */*' -H 'connection: keep-alive' http://www.google.ru/
|
|
36
|
+
```
|
|
37
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
curlify3/__init__.py,sha256=G1M_12HP92eYzc-g52VWsAmLq0LYLz-jCCwP_9e5pTc,124
|
|
2
|
+
curlify3/_base.py,sha256=JLYDa4_rUiEnlzltRi8wZryVKxGpvCpe9jUJTZ-LsSg,977
|
|
3
|
+
curlify3/_curl.py,sha256=oyAjHcc9RomIDLMKnayWkmjYYsDF1FRTXwudtpa4IaU,2443
|
|
4
|
+
curlify3/_req_httpx.py,sha256=gxHLwOrHxaNz9kougWYHCL0Qi7rNSN4uRGMnjuDKKW0,610
|
|
5
|
+
curlify3/_req_requests.py,sha256=rNy1Uqqa5SmGfJAHzEeJl-X5uw9Cypfo5IxA1CtkK8A,371
|
|
6
|
+
curlify3/_req_starlette.py,sha256=6ZY6k8XOqoliPHprzIFRDzbUOj_x0E0K_eOt66Tb8M4,494
|
|
7
|
+
curlify3/_utils.py,sha256=WLmsZgBRuczIp_gtjD56ZJcKGrMCI6oT84RCgaxhofc,1246
|
|
8
|
+
curlify3-0.1rc1.dist-info/METADATA,sha256=u7fDVXkzlLZ-kvgHl0-LI3tg-9Q2qe32b1PTdWQEVrQ,1156
|
|
9
|
+
curlify3-0.1rc1.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
|
10
|
+
curlify3-0.1rc1.dist-info/RECORD,,
|