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.
Files changed (29) hide show
  1. {redc-0.1.3 → redc-0.1.5}/.github/workflows/before-all.sh +1 -1
  2. {redc-0.1.3 → redc-0.1.5}/PKG-INFO +3 -3
  3. {redc-0.1.3 → redc-0.1.5}/README.md +1 -1
  4. {redc-0.1.3 → redc-0.1.5}/pyproject.toml +1 -1
  5. {redc-0.1.3 → redc-0.1.5}/redc/__init__.py +8 -8
  6. {redc-0.1.3 → redc-0.1.5}/redc/client.py +34 -20
  7. {redc-0.1.3 → redc-0.1.5}/redc/ext/redc.cpp +31 -11
  8. {redc-0.1.3 → redc-0.1.5}/redc/ext/redc.h +10 -6
  9. redc-0.1.5/redc/ext/utils/memoryview.h +36 -0
  10. redc-0.1.5/redc/utils/__init__.py +13 -0
  11. redc-0.1.5/redc/utils/_io_utils.py +11 -0
  12. redc-0.1.3/redc/utils/__init__.py +0 -5
  13. {redc-0.1.3 → redc-0.1.5}/.clang-format +0 -0
  14. {redc-0.1.3 → redc-0.1.5}/.github/workflows/build_wheels.yml +0 -0
  15. {redc-0.1.3 → redc-0.1.5}/.gitignore +0 -0
  16. {redc-0.1.3 → redc-0.1.5}/CMake/PreventInSourceBuild.cmake +0 -0
  17. {redc-0.1.3 → redc-0.1.5}/CMakeLists.txt +0 -0
  18. {redc-0.1.3 → redc-0.1.5}/LICENSE +0 -0
  19. {redc-0.1.3 → redc-0.1.5}/assets/images/redc-logo.png +0 -0
  20. {redc-0.1.3 → redc-0.1.5}/redc/callback.py +0 -0
  21. {redc-0.1.3 → redc-0.1.5}/redc/callbacks.py +0 -0
  22. {redc-0.1.3 → redc-0.1.5}/redc/codes.py +0 -0
  23. {redc-0.1.3 → redc-0.1.5}/redc/exceptions/__init__.py +0 -0
  24. {redc-0.1.3 → redc-0.1.5}/redc/ext/utils/concurrentqueue.h +0 -0
  25. {redc-0.1.3 → redc-0.1.5}/redc/ext/utils/curl_utils.h +0 -0
  26. {redc-0.1.3 → redc-0.1.5}/redc/response.py +0 -0
  27. {redc-0.1.3 → redc-0.1.5}/redc/utils/headers.py +0 -0
  28. {redc-0.1.3 → redc-0.1.5}/redc/utils/http.py +0 -0
  29. {redc-0.1.3 → redc-0.1.5}/redc/utils/json_encoder.py +0 -0
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bash
2
2
 
3
- CURL_VERSION="8.12.1"
3
+ CURL_VERSION="8.13.0"
4
4
 
5
5
  # deps
6
6
  yum install wget gcc make libpsl-devel libidn-devel zlib-devel libnghttp2-devel perl-IPC-Cmd -y
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: redc
3
- Version: 0.1.3
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
- [![Version](https://img.shields.io/pypi/v/redc?style=flat&logo=curl&logoColor=red&color=red)](https://pypi.org/project/RedC) [![CURL version](https://img.shields.io/badge/Curl-v8.12.0-red?logo=curl)](https://curl.se/ch/8.12.0.html) [![Downloads](https://static.pepy.tech/personalized-badge/redc?period=month&units=none&left_color=grey&right_color=brightgreen&left_text=Downloads)](https://pepy.tech/project/redc)
17
+ [![Version](https://img.shields.io/pypi/v/redc?style=flat&logo=curl&logoColor=red&color=red)](https://pypi.org/project/RedC) [![CURL version](https://img.shields.io/badge/Curl-v8.13.0-red?logo=curl)](https://curl.se/ch/8.13.0.html) [![Downloads](https://static.pepy.tech/personalized-badge/redc?period=month&units=none&left_color=grey&right_color=brightgreen&left_text=Downloads)](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
- [![Version](https://img.shields.io/pypi/v/redc?style=flat&logo=curl&logoColor=red&color=red)](https://pypi.org/project/RedC) [![CURL version](https://img.shields.io/badge/Curl-v8.12.0-red?logo=curl)](https://curl.se/ch/8.12.0.html) [![Downloads](https://static.pepy.tech/personalized-badge/redc?period=month&units=none&left_color=grey&right_color=brightgreen&left_text=Downloads)](https://pepy.tech/project/redc)
5
+ [![Version](https://img.shields.io/pypi/v/redc?style=flat&logo=curl&logoColor=red&color=red)](https://pypi.org/project/RedC) [![CURL version](https://img.shields.io/badge/Curl-v8.13.0-red?logo=curl)](https://curl.se/ch/8.13.0.html) [![Downloads](https://static.pepy.tech/personalized-badge/redc?period=month&units=none&left_color=grey&right_color=brightgreen&left_text=Downloads)](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.3"
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 .callbacks import StreamCallback, ProgressCallback
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
- "StreamCallback",
14
- "ProgressCallback",
15
- "utils",
14
+ "HTTPError",
15
+ "Response",
16
16
  ]
17
17
 
18
- __version__ = "0.1.3"
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
- from .callbacks import StreamCallback, ProgressCallback
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, Headers
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 to send in the request body. Default is ``None``
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 not isinstance(data, dict):
216
- raise TypeError("data must be of type dict[str, str]")
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 = {**self.__default_headers, **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 to send in the request body. Default is ``None``
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 to send in the request body. Default is ``None``
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 to send in the request body. Default is ``None``
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 &data,
55
- const py_object &files, const py_object &headers, const long &timeout_ms,
56
- const long &connect_timeout_ms, const bool &allow_redirect, const char *proxy_url,
57
- const bool &verify, const char *ca_cert_path, const py_object &stream_callback,
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("data") = nb::none(),
350
- arg("files") = nb::none(), arg("headers") = nb::none(), arg("timeout_ms") = 60 * 1000,
351
- arg("connect_timeout_ms") = 0, arg("allow_redirect") = true, arg("proxy_url") = "", arg("verify") = true,
352
- arg("ca_cert_path") = "", arg("stream_callback") = nb::none(), arg("progress_callback") = nb::none(),
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 = "", const py_object &data = nb::none(),
60
- const py_object &files = nb::none(), const py_object &headers = nb::none(),
61
- const long &timeout_ms = 60 * 1000, const long &connect_timeout_ms = 0,
62
- const bool &allow_redirect = true, const char *proxy_url = "", const bool &verify = true,
63
- const char *ca_cert_path = "", const py_object &stream_callback = nb::none(),
64
- const py_object &progress_callback = nb::none(), const bool &verbose = false);
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
@@ -0,0 +1,11 @@
1
+ from os import SEEK_END
2
+ from typing import BinaryIO
3
+
4
+
5
+ def get_fsize(f: BinaryIO) -> int:
6
+ """Get the size of a file-like object"""
7
+ current_pos = f.tell()
8
+ f.seek(0, SEEK_END)
9
+ f_size = f.tell()
10
+ f.seek(current_pos)
11
+ return f_size
@@ -1,5 +0,0 @@
1
- __all__ = ["check_key_dict", "Headers", "json_dumps", "json_loads", "parse_base_url"]
2
-
3
- from .headers import check_key_dict, Headers
4
- from .json_encoder import json_dumps, json_loads
5
- from .http import parse_base_url
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