redc 0.1.3__tar.gz → 0.1.4__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.4}/.github/workflows/before-all.sh +1 -1
- {redc-0.1.3 → redc-0.1.4}/PKG-INFO +3 -3
- {redc-0.1.3 → redc-0.1.4}/README.md +1 -1
- {redc-0.1.3 → redc-0.1.4}/pyproject.toml +1 -1
- {redc-0.1.3 → redc-0.1.4}/redc/__init__.py +8 -8
- {redc-0.1.3 → redc-0.1.4}/redc/client.py +30 -19
- {redc-0.1.3 → redc-0.1.4}/redc/ext/redc.cpp +30 -10
- {redc-0.1.3 → redc-0.1.4}/redc/ext/redc.h +10 -6
- redc-0.1.4/redc/ext/utils/memoryview.h +36 -0
- redc-0.1.4/redc/utils/__init__.py +13 -0
- redc-0.1.4/redc/utils/_io_utils.py +11 -0
- redc-0.1.3/redc/utils/__init__.py +0 -5
- {redc-0.1.3 → redc-0.1.4}/.clang-format +0 -0
- {redc-0.1.3 → redc-0.1.4}/.github/workflows/build_wheels.yml +0 -0
- {redc-0.1.3 → redc-0.1.4}/.gitignore +0 -0
- {redc-0.1.3 → redc-0.1.4}/CMake/PreventInSourceBuild.cmake +0 -0
- {redc-0.1.3 → redc-0.1.4}/CMakeLists.txt +0 -0
- {redc-0.1.3 → redc-0.1.4}/LICENSE +0 -0
- {redc-0.1.3 → redc-0.1.4}/assets/images/redc-logo.png +0 -0
- {redc-0.1.3 → redc-0.1.4}/redc/callback.py +0 -0
- {redc-0.1.3 → redc-0.1.4}/redc/callbacks.py +0 -0
- {redc-0.1.3 → redc-0.1.4}/redc/codes.py +0 -0
- {redc-0.1.3 → redc-0.1.4}/redc/exceptions/__init__.py +0 -0
- {redc-0.1.3 → redc-0.1.4}/redc/ext/utils/concurrentqueue.h +0 -0
- {redc-0.1.3 → redc-0.1.4}/redc/ext/utils/curl_utils.h +0 -0
- {redc-0.1.3 → redc-0.1.4}/redc/response.py +0 -0
- {redc-0.1.3 → redc-0.1.4}/redc/utils/headers.py +0 -0
- {redc-0.1.3 → redc-0.1.4}/redc/utils/http.py +0 -0
- {redc-0.1.3 → redc-0.1.4}/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.4
|
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.4"
|
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.4"
|
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):
|
@@ -247,6 +256,8 @@ class Client:
|
|
247
256
|
method=method,
|
248
257
|
url=url,
|
249
258
|
raw_data=form or json or "",
|
259
|
+
file_stream=file_stream,
|
260
|
+
file_size=file_size,
|
250
261
|
data=data,
|
251
262
|
files=files,
|
252
263
|
headers=headers,
|
@@ -391,7 +402,7 @@ class Client:
|
|
391
402
|
url: str,
|
392
403
|
form: dict = None,
|
393
404
|
json: dict = None,
|
394
|
-
data: dict[str, str] = None,
|
405
|
+
data: Union[dict[str, str], BinaryIO] = None,
|
395
406
|
files: dict[str, str] = None,
|
396
407
|
headers: dict[str, str] = None,
|
397
408
|
timeout: tuple = None,
|
@@ -424,8 +435,8 @@ class Client:
|
|
424
435
|
json (``dict``, *optional*):
|
425
436
|
JSON data to send in the request body. Default is ``None``
|
426
437
|
|
427
|
-
data (``dict[str, str]``, *optional*):
|
428
|
-
Multipart form data
|
438
|
+
data (``dict[str, str]`` || ``BinaryIO``, *optional*):
|
439
|
+
Multipart form data dict or a binary file-like object (requires ``readinto``). Default is ``None``
|
429
440
|
|
430
441
|
files (``dict[str, str]``, *optional*):
|
431
442
|
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 +492,7 @@ class Client:
|
|
481
492
|
url: str,
|
482
493
|
form: dict = None,
|
483
494
|
json: dict = None,
|
484
|
-
data: dict[str, str] = None,
|
495
|
+
data: Union[dict[str, str], BinaryIO] = None,
|
485
496
|
files: dict[str, str] = None,
|
486
497
|
headers: dict[str, str] = None,
|
487
498
|
timeout: tuple = None,
|
@@ -514,8 +525,8 @@ class Client:
|
|
514
525
|
json (``dict``, *optional*):
|
515
526
|
JSON data to send in the request body. Default is ``None``
|
516
527
|
|
517
|
-
data (``dict[str, str]``, *optional*):
|
518
|
-
Multipart form data
|
528
|
+
data (``dict[str, str]`` || ``BinaryIO``, *optional*):
|
529
|
+
Multipart form data dict or a binary file-like object (requires ``readinto``). Default is ``None``
|
519
530
|
|
520
531
|
files (``dict[str, str]``, *optional*):
|
521
532
|
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 +582,7 @@ class Client:
|
|
571
582
|
url: str,
|
572
583
|
form: dict = None,
|
573
584
|
json: dict = None,
|
574
|
-
data: dict[str, str] = None,
|
585
|
+
data: Union[dict[str, str], BinaryIO] = None,
|
575
586
|
files: dict[str, str] = None,
|
576
587
|
headers: dict[str, str] = None,
|
577
588
|
timeout: tuple = None,
|
@@ -604,8 +615,8 @@ class Client:
|
|
604
615
|
json (``dict``, *optional*):
|
605
616
|
JSON data to send in the request body. Default is ``None``
|
606
617
|
|
607
|
-
data (``dict[str, str]``, *optional*):
|
608
|
-
Multipart form data
|
618
|
+
data (``dict[str, str]`` || ``BinaryIO``, *optional*):
|
619
|
+
Multipart form data dict or a binary file-like object (requires ``readinto``). Default is ``None``
|
609
620
|
|
610
621
|
files (``dict[str, str]``, *optional*):
|
611
622
|
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)) {
|
@@ -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
|