redc 0.1.3__tar.gz → 0.1.5__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.
- {redc-0.1.3 → redc-0.1.5}/.github/workflows/before-all.sh +1 -1
- {redc-0.1.3 → redc-0.1.5}/PKG-INFO +3 -3
- {redc-0.1.3 → redc-0.1.5}/README.md +1 -1
- {redc-0.1.3 → redc-0.1.5}/pyproject.toml +1 -1
- {redc-0.1.3 → redc-0.1.5}/redc/__init__.py +8 -8
- {redc-0.1.3 → redc-0.1.5}/redc/client.py +34 -20
- {redc-0.1.3 → redc-0.1.5}/redc/ext/redc.cpp +31 -11
- {redc-0.1.3 → redc-0.1.5}/redc/ext/redc.h +10 -6
- redc-0.1.5/redc/ext/utils/memoryview.h +36 -0
- redc-0.1.5/redc/utils/__init__.py +13 -0
- redc-0.1.5/redc/utils/_io_utils.py +11 -0
- redc-0.1.3/redc/utils/__init__.py +0 -5
- {redc-0.1.3 → redc-0.1.5}/.clang-format +0 -0
- {redc-0.1.3 → redc-0.1.5}/.github/workflows/build_wheels.yml +0 -0
- {redc-0.1.3 → redc-0.1.5}/.gitignore +0 -0
- {redc-0.1.3 → redc-0.1.5}/CMake/PreventInSourceBuild.cmake +0 -0
- {redc-0.1.3 → redc-0.1.5}/CMakeLists.txt +0 -0
- {redc-0.1.3 → redc-0.1.5}/LICENSE +0 -0
- {redc-0.1.3 → redc-0.1.5}/assets/images/redc-logo.png +0 -0
- {redc-0.1.3 → redc-0.1.5}/redc/callback.py +0 -0
- {redc-0.1.3 → redc-0.1.5}/redc/callbacks.py +0 -0
- {redc-0.1.3 → redc-0.1.5}/redc/codes.py +0 -0
- {redc-0.1.3 → redc-0.1.5}/redc/exceptions/__init__.py +0 -0
- {redc-0.1.3 → redc-0.1.5}/redc/ext/utils/concurrentqueue.h +0 -0
- {redc-0.1.3 → redc-0.1.5}/redc/ext/utils/curl_utils.h +0 -0
- {redc-0.1.3 → redc-0.1.5}/redc/response.py +0 -0
- {redc-0.1.3 → redc-0.1.5}/redc/utils/headers.py +0 -0
- {redc-0.1.3 → redc-0.1.5}/redc/utils/http.py +0 -0
- {redc-0.1.3 → redc-0.1.5}/redc/utils/json_encoder.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.2
|
2
2
|
Name: redc
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.5
|
4
4
|
Summary: RedC is a high-performance, asynchronous HTTP client library for Python, built on top of the powerful curl library
|
5
5
|
Keywords: asyncio,http,client,http-client,curl,libcurl
|
6
6
|
Author-Email: AYMEN Mohammed <let.me.code.safe@gmail.com>
|
@@ -14,7 +14,7 @@ Description-Content-Type: text/markdown
|
|
14
14
|
<img src="https://raw.githubusercontent.com/AYMENJD/redc/refs/heads/main/assets/images/redc-logo.png">
|
15
15
|
</div>
|
16
16
|
|
17
|
-
[](https://pypi.org/project/RedC) [](https://pypi.org/project/RedC) [](https://curl.se/ch/8.13.0.html) [](https://pepy.tech/project/redc)
|
18
18
|
|
19
19
|
**RedC** is a **high-performance**, asynchronous **HTTP** client library for **Python**, built on top of the powerful **curl** library. It provides a simple and intuitive interface for making HTTP requests and handling responses
|
20
20
|
|
@@ -2,7 +2,7 @@
|
|
2
2
|
<img src="https://raw.githubusercontent.com/AYMENJD/redc/refs/heads/main/assets/images/redc-logo.png">
|
3
3
|
</div>
|
4
4
|
|
5
|
-
[](https://pypi.org/project/RedC) [](https://pypi.org/project/RedC) [](https://curl.se/ch/8.13.0.html) [](https://pepy.tech/project/redc)
|
6
6
|
|
7
7
|
**RedC** is a **high-performance**, asynchronous **HTTP** client library for **Python**, built on top of the powerful **curl** library. It provides a simple and intuitive interface for making HTTP requests and handling responses
|
8
8
|
|
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "redc"
|
7
|
-
version = "0.1.
|
7
|
+
version = "0.1.5"
|
8
8
|
description = "RedC is a high-performance, asynchronous HTTP client library for Python, built on top of the powerful curl library"
|
9
9
|
readme = "README.md"
|
10
10
|
authors = [{ name = "AYMEN Mohammed", email = "let.me.code.safe@gmail.com" }]
|
@@ -1,21 +1,21 @@
|
|
1
|
-
from .
|
1
|
+
from . import utils
|
2
|
+
from .callbacks import ProgressCallback, StreamCallback
|
2
3
|
from .client import Client
|
3
4
|
from .codes import HTTPStatus
|
4
5
|
from .exceptions import HTTPError
|
5
6
|
from .response import Response
|
6
|
-
from . import utils
|
7
7
|
|
8
8
|
__all__ = [
|
9
|
+
"utils",
|
10
|
+
"ProgressCallback",
|
11
|
+
"StreamCallback",
|
9
12
|
"Client",
|
10
|
-
"Response",
|
11
|
-
"HTTPError",
|
12
13
|
"HTTPStatus",
|
13
|
-
"
|
14
|
-
"
|
15
|
-
"utils",
|
14
|
+
"HTTPError",
|
15
|
+
"Response",
|
16
16
|
]
|
17
17
|
|
18
|
-
__version__ = "0.1.
|
18
|
+
__version__ = "0.1.5"
|
19
19
|
__copyright__ = "Copyright (c) 2025 RedC, AYMENJD"
|
20
20
|
__license__ = "MIT License"
|
21
21
|
|
@@ -1,12 +1,13 @@
|
|
1
|
+
import asyncio
|
2
|
+
from typing import BinaryIO, Union
|
1
3
|
from urllib.parse import urlencode
|
2
4
|
|
3
|
-
|
5
|
+
import redc
|
6
|
+
|
7
|
+
from .callbacks import ProgressCallback, StreamCallback
|
4
8
|
from .redc_ext import RedC
|
5
9
|
from .response import Response
|
6
|
-
from .utils import json_dumps, parse_base_url,
|
7
|
-
|
8
|
-
import asyncio
|
9
|
-
import redc
|
10
|
+
from .utils import Headers, json_dumps, parse_base_url, get_fsize
|
10
11
|
|
11
12
|
|
12
13
|
class Client:
|
@@ -117,7 +118,7 @@ class Client:
|
|
117
118
|
url: str,
|
118
119
|
form: dict = None,
|
119
120
|
json: dict = None,
|
120
|
-
data: dict[str, str] = None,
|
121
|
+
data: Union[dict[str, str], BinaryIO] = None,
|
121
122
|
files: dict[str, str] = None,
|
122
123
|
headers: dict[str, str] = None,
|
123
124
|
timeout: tuple = None,
|
@@ -149,8 +150,8 @@ class Client:
|
|
149
150
|
json (``dict``, *optional*):
|
150
151
|
JSON data to send in the request body. Default is ``None``
|
151
152
|
|
152
|
-
data (``dict[str, str]``, *optional*):
|
153
|
-
Multipart form data
|
153
|
+
data (``dict[str, str]`` || ``BinaryIO``, *optional*):
|
154
|
+
Multipart form data dict or a binary file-like object (requires ``readinto``). Default is ``None``
|
154
155
|
|
155
156
|
files (``dict[str, str]``, *optional*):
|
156
157
|
A dictionary specifying files to upload as part of a multipart form request, ``key`` is the form field and ``value`` is string containing the file path
|
@@ -211,9 +212,17 @@ class Client:
|
|
211
212
|
else:
|
212
213
|
raise TypeError("json must be of type dict[str, str]")
|
213
214
|
|
215
|
+
file_stream = None
|
216
|
+
file_size = 0
|
214
217
|
if data is not None:
|
215
|
-
if
|
216
|
-
|
218
|
+
if hasattr(data, "readinto"):
|
219
|
+
file_stream = data
|
220
|
+
file_size = get_fsize(file_stream)
|
221
|
+
data = None
|
222
|
+
elif not isinstance(data, dict):
|
223
|
+
raise TypeError(
|
224
|
+
"data must be either dict[str, str] or a file-like object with readinto method"
|
225
|
+
)
|
217
226
|
|
218
227
|
if files is not None:
|
219
228
|
if not isinstance(files, dict):
|
@@ -231,7 +240,10 @@ class Client:
|
|
231
240
|
|
232
241
|
if headers is not None:
|
233
242
|
if isinstance(headers, dict):
|
234
|
-
headers = {
|
243
|
+
headers = {
|
244
|
+
**self.__default_headers,
|
245
|
+
**{k.lower(): v for k, v in headers.items()},
|
246
|
+
}
|
235
247
|
headers = [f"{k}: {v}" for k, v in headers.items()]
|
236
248
|
else:
|
237
249
|
raise TypeError("headers must be of type dict[str, str]")
|
@@ -247,6 +259,8 @@ class Client:
|
|
247
259
|
method=method,
|
248
260
|
url=url,
|
249
261
|
raw_data=form or json or "",
|
262
|
+
file_stream=file_stream,
|
263
|
+
file_size=file_size,
|
250
264
|
data=data,
|
251
265
|
files=files,
|
252
266
|
headers=headers,
|
@@ -391,7 +405,7 @@ class Client:
|
|
391
405
|
url: str,
|
392
406
|
form: dict = None,
|
393
407
|
json: dict = None,
|
394
|
-
data: dict[str, str] = None,
|
408
|
+
data: Union[dict[str, str], BinaryIO] = None,
|
395
409
|
files: dict[str, str] = None,
|
396
410
|
headers: dict[str, str] = None,
|
397
411
|
timeout: tuple = None,
|
@@ -424,8 +438,8 @@ class Client:
|
|
424
438
|
json (``dict``, *optional*):
|
425
439
|
JSON data to send in the request body. Default is ``None``
|
426
440
|
|
427
|
-
data (``dict[str, str]``, *optional*):
|
428
|
-
Multipart form data
|
441
|
+
data (``dict[str, str]`` || ``BinaryIO``, *optional*):
|
442
|
+
Multipart form data dict or a binary file-like object (requires ``readinto``). Default is ``None``
|
429
443
|
|
430
444
|
files (``dict[str, str]``, *optional*):
|
431
445
|
A dictionary specifying files to upload as part of a multipart form request, ``key`` is the form field and ``value`` is string containing the file path
|
@@ -481,7 +495,7 @@ class Client:
|
|
481
495
|
url: str,
|
482
496
|
form: dict = None,
|
483
497
|
json: dict = None,
|
484
|
-
data: dict[str, str] = None,
|
498
|
+
data: Union[dict[str, str], BinaryIO] = None,
|
485
499
|
files: dict[str, str] = None,
|
486
500
|
headers: dict[str, str] = None,
|
487
501
|
timeout: tuple = None,
|
@@ -514,8 +528,8 @@ class Client:
|
|
514
528
|
json (``dict``, *optional*):
|
515
529
|
JSON data to send in the request body. Default is ``None``
|
516
530
|
|
517
|
-
data (``dict[str, str]``, *optional*):
|
518
|
-
Multipart form data
|
531
|
+
data (``dict[str, str]`` || ``BinaryIO``, *optional*):
|
532
|
+
Multipart form data dict or a binary file-like object (requires ``readinto``). Default is ``None``
|
519
533
|
|
520
534
|
files (``dict[str, str]``, *optional*):
|
521
535
|
A dictionary specifying files to upload as part of a multipart form request, ``key`` is the form field and ``value`` is string containing the file path
|
@@ -571,7 +585,7 @@ class Client:
|
|
571
585
|
url: str,
|
572
586
|
form: dict = None,
|
573
587
|
json: dict = None,
|
574
|
-
data: dict[str, str] = None,
|
588
|
+
data: Union[dict[str, str], BinaryIO] = None,
|
575
589
|
files: dict[str, str] = None,
|
576
590
|
headers: dict[str, str] = None,
|
577
591
|
timeout: tuple = None,
|
@@ -604,8 +618,8 @@ class Client:
|
|
604
618
|
json (``dict``, *optional*):
|
605
619
|
JSON data to send in the request body. Default is ``None``
|
606
620
|
|
607
|
-
data (``dict[str, str]``, *optional*):
|
608
|
-
Multipart form data
|
621
|
+
data (``dict[str, str]`` || ``BinaryIO``, *optional*):
|
622
|
+
Multipart form data dict or a binary file-like object (requires ``readinto``). Default is ``None``
|
609
623
|
|
610
624
|
files (``dict[str, str]``, *optional*):
|
611
625
|
A dictionary specifying files to upload as part of a multipart form request, ``key`` is the form field and ``value`` is string containing the file path
|
@@ -1,5 +1,6 @@
|
|
1
1
|
#include "redc.h"
|
2
2
|
#include "utils/curl_utils.h"
|
3
|
+
#include "utils/memoryview.h"
|
3
4
|
#include <iostream>
|
4
5
|
#include <stdexcept>
|
5
6
|
|
@@ -51,11 +52,11 @@ void RedC::close() {
|
|
51
52
|
}
|
52
53
|
}
|
53
54
|
|
54
|
-
py_object RedC::request(const char *method, const char *url, const char *raw_data, const py_object &
|
55
|
-
const py_object &
|
56
|
-
const long &
|
57
|
-
const bool &verify, const char *ca_cert_path,
|
58
|
-
const py_object &progress_callback, const bool &verbose) {
|
55
|
+
py_object RedC::request(const char *method, const char *url, const char *raw_data, const py_object &file_stream,
|
56
|
+
const long &file_size, const py_object &data, const py_object &files, const py_object &headers,
|
57
|
+
const long &timeout_ms, const long &connect_timeout_ms, const bool &allow_redirect,
|
58
|
+
const char *proxy_url, const bool &verify, const char *ca_cert_path,
|
59
|
+
const py_object &stream_callback, const py_object &progress_callback, const bool &verbose) {
|
59
60
|
CHECK_RUNNING();
|
60
61
|
|
61
62
|
if (isNullOrEmpty(method) || isNullOrEmpty(url)) {
|
@@ -126,8 +127,8 @@ py_object RedC::request(const char *method, const char *url, const char *raw_dat
|
|
126
127
|
|
127
128
|
for (auto const &it : dict_obj) {
|
128
129
|
curl_mimepart *part = curl_mime_addpart(curl_mime_.mime);
|
129
|
-
curl_mime_name(part, nb::str(it.first).c_str());
|
130
130
|
curl_mime_data(part, nb::str(it.second).c_str(), CURL_ZERO_TERMINATED);
|
131
|
+
curl_mime_name(part, nb::str(it.first).c_str());
|
131
132
|
}
|
132
133
|
}
|
133
134
|
|
@@ -169,9 +170,19 @@ py_object RedC::request(const char *method, const char *url, const char *raw_dat
|
|
169
170
|
d.curl_mime_ = std::move(curl_mime_);
|
170
171
|
|
171
172
|
curl_easy_setopt(easy, CURLOPT_HEADERDATA, &d);
|
173
|
+
|
172
174
|
if (!is_nobody) {
|
173
175
|
curl_easy_setopt(easy, CURLOPT_WRITEDATA, &d);
|
174
176
|
|
177
|
+
if (!file_stream.is_none()) {
|
178
|
+
d.file_stream = file_stream;
|
179
|
+
|
180
|
+
curl_easy_setopt(easy, CURLOPT_UPLOAD, 1L);
|
181
|
+
curl_easy_setopt(easy, CURLOPT_READDATA, &d);
|
182
|
+
curl_easy_setopt(easy, CURLOPT_READFUNCTION, &RedC::read_callback);
|
183
|
+
curl_easy_setopt(easy, CURLOPT_INFILESIZE_LARGE, (curl_off_t)file_size);
|
184
|
+
}
|
185
|
+
|
175
186
|
if (!stream_callback.is_none()) {
|
176
187
|
d.stream_callback = stream_callback;
|
177
188
|
d.has_stream_callback = true;
|
@@ -304,6 +315,15 @@ void RedC::CHECK_RUNNING() {
|
|
304
315
|
}
|
305
316
|
}
|
306
317
|
|
318
|
+
size_t RedC::read_callback(char *buffer, size_t size, size_t nitems, Data *clientp) {
|
319
|
+
acq_gil gil;
|
320
|
+
|
321
|
+
auto memview = nb::memoryview::from_memory(buffer, size * nitems);
|
322
|
+
auto result = clientp->file_stream.attr("readinto")(memview);
|
323
|
+
|
324
|
+
return nb::cast<curl_off_t>(result);
|
325
|
+
}
|
326
|
+
|
307
327
|
size_t RedC::header_callback(char *buffer, size_t size, size_t nitems, Data *clientp) {
|
308
328
|
size_t total_size = size * nitems;
|
309
329
|
clientp->headers.insert(clientp->headers.end(), buffer, buffer + total_size);
|
@@ -346,10 +366,10 @@ NB_MODULE(redc_ext, m) {
|
|
346
366
|
nb::class_<RedC>(m, "RedC")
|
347
367
|
.def(nb::init<const long &>())
|
348
368
|
.def("is_running", &RedC::is_running)
|
349
|
-
.def("request", &RedC::request, arg("method"), arg("url"), arg("raw_data") = "", arg("
|
350
|
-
arg("
|
351
|
-
arg("
|
352
|
-
arg("
|
353
|
-
arg("verbose") = false)
|
369
|
+
.def("request", &RedC::request, arg("method"), arg("url"), arg("raw_data") = "", arg("file_stream") = nb::none(),
|
370
|
+
arg("file_size") = 0, arg("data") = nb::none(), arg("files") = nb::none(), arg("headers") = nb::none(),
|
371
|
+
arg("timeout_ms") = 60 * 1000, arg("connect_timeout_ms") = 0, arg("allow_redirect") = true,
|
372
|
+
arg("proxy_url") = "", arg("verify") = true, arg("ca_cert_path") = "", arg("stream_callback") = nb::none(),
|
373
|
+
arg("progress_callback") = nb::none(), arg("verbose") = false)
|
354
374
|
.def("close", &RedC::close, nb::call_guard<nb::gil_scoped_release>());
|
355
375
|
}
|
@@ -37,6 +37,7 @@ struct Data {
|
|
37
37
|
py_object loop;
|
38
38
|
py_object stream_callback{nb::none()};
|
39
39
|
py_object progress_callback{nb::none()};
|
40
|
+
py_object file_stream{nb::none()};
|
40
41
|
|
41
42
|
bool has_stream_callback{false};
|
42
43
|
bool has_progress_callback{false};
|
@@ -56,12 +57,14 @@ class RedC {
|
|
56
57
|
bool is_running();
|
57
58
|
void close();
|
58
59
|
|
59
|
-
py_object request(const char *method, const char *url, const char *raw_data = "",
|
60
|
-
const py_object &
|
61
|
-
const
|
62
|
-
const
|
63
|
-
const
|
64
|
-
const
|
60
|
+
py_object request(const char *method, const char *url, const char *raw_data = "",
|
61
|
+
const py_object &file_stream = nb::none(), const long &file_size = 0,
|
62
|
+
const py_object &data = nb::none(), const py_object &files = nb::none(),
|
63
|
+
const py_object &headers = nb::none(), const long &timeout_ms = 60 * 1000,
|
64
|
+
const long &connect_timeout_ms = 0, const bool &allow_redirect = true, const char *proxy_url = "",
|
65
|
+
const bool &verify = true, const char *ca_cert_path = "",
|
66
|
+
const py_object &stream_callback = nb::none(), const py_object &progress_callback = nb::none(),
|
67
|
+
const bool &verbose = false);
|
65
68
|
|
66
69
|
private:
|
67
70
|
int still_running_{0};
|
@@ -82,6 +85,7 @@ class RedC {
|
|
82
85
|
void cleanup();
|
83
86
|
void CHECK_RUNNING();
|
84
87
|
|
88
|
+
static size_t read_callback(char *buffer, size_t size, size_t nitems, Data *clientp);
|
85
89
|
static size_t header_callback(char *buffer, size_t size, size_t nitems, Data *clientp);
|
86
90
|
static size_t progress_callback(Data *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal,
|
87
91
|
curl_off_t ulnow);
|
@@ -0,0 +1,36 @@
|
|
1
|
+
#include <nanobind/nanobind.h>
|
2
|
+
|
3
|
+
NAMESPACE_BEGIN(NB_NAMESPACE)
|
4
|
+
|
5
|
+
// https://github.com/lief-project/LIEF/blob/abcf929efb748c7846dd59007cbb807e108db311/api/python/src/nanobind/extra/memoryview.hpp
|
6
|
+
class memoryview : public object {
|
7
|
+
using ssize_t = Py_ssize_t;
|
8
|
+
|
9
|
+
public:
|
10
|
+
NB_OBJECT(memoryview, object, "memoryview", PyMemoryView_Check)
|
11
|
+
|
12
|
+
memoryview(const object &o)
|
13
|
+
: object(check_(o) ? o.inc_ref().ptr() : PyMemoryView_FromObject(o.ptr()), detail::steal_t{}) {
|
14
|
+
if (!m_ptr)
|
15
|
+
detail::raise_python_error();
|
16
|
+
}
|
17
|
+
|
18
|
+
memoryview(object &&o) : object(check_(o) ? o.release().ptr() : PyMemoryView_FromObject(o.ptr()), detail::steal_t{}) {
|
19
|
+
if (!m_ptr)
|
20
|
+
detail::raise_python_error();
|
21
|
+
}
|
22
|
+
|
23
|
+
static memoryview from_memory(void *mem, ssize_t size, bool readonly = false) {
|
24
|
+
PyObject *ptr = PyMemoryView_FromMemory(reinterpret_cast<char *>(mem), size, (readonly) ? PyBUF_READ : PyBUF_WRITE);
|
25
|
+
if (!ptr) {
|
26
|
+
detail::fail("Could not allocate memoryview object!");
|
27
|
+
}
|
28
|
+
return memoryview(object(ptr, detail::steal_t{}));
|
29
|
+
}
|
30
|
+
|
31
|
+
static memoryview from_memory(const void *mem, ssize_t size) {
|
32
|
+
return memoryview::from_memory(const_cast<void *>(mem), size, true);
|
33
|
+
}
|
34
|
+
};
|
35
|
+
|
36
|
+
NAMESPACE_END(NB_NAMESPACE)
|
@@ -0,0 +1,13 @@
|
|
1
|
+
__all__ = [
|
2
|
+
"get_fsize",
|
3
|
+
"Headers",
|
4
|
+
"check_key_dict",
|
5
|
+
"parse_base_url",
|
6
|
+
"json_dumps",
|
7
|
+
"json_loads",
|
8
|
+
]
|
9
|
+
|
10
|
+
from ._io_utils import get_fsize
|
11
|
+
from .headers import Headers, check_key_dict
|
12
|
+
from .http import parse_base_url
|
13
|
+
from .json_encoder import json_dumps, json_loads
|
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
|