redc 0.1.2__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.
Files changed (29) hide show
  1. {redc-0.1.2 → redc-0.1.4}/.github/workflows/before-all.sh +1 -1
  2. {redc-0.1.2 → redc-0.1.4}/PKG-INFO +3 -3
  3. {redc-0.1.2 → redc-0.1.4}/README.md +1 -1
  4. {redc-0.1.2 → redc-0.1.4}/pyproject.toml +1 -1
  5. {redc-0.1.2 → redc-0.1.4}/redc/__init__.py +8 -8
  6. {redc-0.1.2 → redc-0.1.4}/redc/client.py +30 -19
  7. {redc-0.1.2 → redc-0.1.4}/redc/ext/redc.cpp +30 -10
  8. {redc-0.1.2 → redc-0.1.4}/redc/ext/redc.h +10 -6
  9. redc-0.1.4/redc/ext/utils/memoryview.h +36 -0
  10. redc-0.1.4/redc/utils/__init__.py +13 -0
  11. redc-0.1.4/redc/utils/_io_utils.py +11 -0
  12. redc-0.1.2/redc/utils/__init__.py +0 -5
  13. {redc-0.1.2 → redc-0.1.4}/.clang-format +0 -0
  14. {redc-0.1.2 → redc-0.1.4}/.github/workflows/build_wheels.yml +0 -0
  15. {redc-0.1.2 → redc-0.1.4}/.gitignore +0 -0
  16. {redc-0.1.2 → redc-0.1.4}/CMake/PreventInSourceBuild.cmake +0 -0
  17. {redc-0.1.2 → redc-0.1.4}/CMakeLists.txt +0 -0
  18. {redc-0.1.2 → redc-0.1.4}/LICENSE +0 -0
  19. {redc-0.1.2 → redc-0.1.4}/assets/images/redc-logo.png +0 -0
  20. {redc-0.1.2 → redc-0.1.4}/redc/callback.py +0 -0
  21. {redc-0.1.2 → redc-0.1.4}/redc/callbacks.py +0 -0
  22. {redc-0.1.2 → redc-0.1.4}/redc/codes.py +0 -0
  23. {redc-0.1.2 → redc-0.1.4}/redc/exceptions/__init__.py +0 -0
  24. {redc-0.1.2 → redc-0.1.4}/redc/ext/utils/concurrentqueue.h +0 -0
  25. {redc-0.1.2 → redc-0.1.4}/redc/ext/utils/curl_utils.h +0 -0
  26. {redc-0.1.2 → redc-0.1.4}/redc/response.py +0 -0
  27. {redc-0.1.2 → redc-0.1.4}/redc/utils/headers.py +0 -0
  28. {redc-0.1.2 → redc-0.1.4}/redc/utils/http.py +0 -0
  29. {redc-0.1.2 → redc-0.1.4}/redc/utils/json_encoder.py +0 -0
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bash
2
2
 
3
- CURL_VERSION="8.12.0"
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.2
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
- [![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.2"
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 .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.2"
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
- 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):
@@ -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 to send in the request body. Default is ``None``
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 to send in the request body. Default is ``None``
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 to send in the request body. Default is ``None``
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 &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)) {
@@ -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