redc 0.1.6__tar.gz → 0.1.7__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 (28) hide show
  1. {redc-0.1.6 → redc-0.1.7}/PKG-INFO +1 -1
  2. {redc-0.1.6 → redc-0.1.7}/pyproject.toml +1 -1
  3. {redc-0.1.6 → redc-0.1.7}/redc/__init__.py +1 -1
  4. {redc-0.1.6 → redc-0.1.7}/redc/ext/redc.cpp +85 -60
  5. {redc-0.1.6 → redc-0.1.7}/redc/ext/redc.h +11 -0
  6. {redc-0.1.6 → redc-0.1.7}/.clang-format +0 -0
  7. {redc-0.1.6 → redc-0.1.7}/.github/workflows/before-all.sh +0 -0
  8. {redc-0.1.6 → redc-0.1.7}/.github/workflows/build_wheels.yml +0 -0
  9. {redc-0.1.6 → redc-0.1.7}/.gitignore +0 -0
  10. {redc-0.1.6 → redc-0.1.7}/CMake/PreventInSourceBuild.cmake +0 -0
  11. {redc-0.1.6 → redc-0.1.7}/CMakeLists.txt +0 -0
  12. {redc-0.1.6 → redc-0.1.7}/LICENSE +0 -0
  13. {redc-0.1.6 → redc-0.1.7}/README.md +0 -0
  14. {redc-0.1.6 → redc-0.1.7}/assets/images/redc-logo.png +0 -0
  15. {redc-0.1.6 → redc-0.1.7}/redc/callback.py +0 -0
  16. {redc-0.1.6 → redc-0.1.7}/redc/callbacks.py +0 -0
  17. {redc-0.1.6 → redc-0.1.7}/redc/client.py +0 -0
  18. {redc-0.1.6 → redc-0.1.7}/redc/codes.py +0 -0
  19. {redc-0.1.6 → redc-0.1.7}/redc/exceptions/__init__.py +0 -0
  20. {redc-0.1.6 → redc-0.1.7}/redc/ext/utils/concurrentqueue.h +0 -0
  21. {redc-0.1.6 → redc-0.1.7}/redc/ext/utils/curl_utils.h +0 -0
  22. {redc-0.1.6 → redc-0.1.7}/redc/ext/utils/memoryview.h +0 -0
  23. {redc-0.1.6 → redc-0.1.7}/redc/response.py +0 -0
  24. {redc-0.1.6 → redc-0.1.7}/redc/utils/__init__.py +0 -0
  25. {redc-0.1.6 → redc-0.1.7}/redc/utils/_io_utils.py +0 -0
  26. {redc-0.1.6 → redc-0.1.7}/redc/utils/headers.py +0 -0
  27. {redc-0.1.6 → redc-0.1.7}/redc/utils/http.py +0 -0
  28. {redc-0.1.6 → redc-0.1.7}/redc/utils/json_encoder.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: redc
3
- Version: 0.1.6
3
+ Version: 0.1.7
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>
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "redc"
7
- version = "0.1.6"
7
+ version = "0.1.7"
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" }]
@@ -15,7 +15,7 @@ __all__ = [
15
15
  "Response",
16
16
  ]
17
17
 
18
- __version__ = "0.1.6"
18
+ __version__ = "0.1.7"
19
19
  __copyright__ = "Copyright (c) 2025 RedC, AYMENJD"
20
20
  __license__ = "MIT License"
21
21
 
@@ -162,42 +162,42 @@ py_object RedC::request(const char *method, const char *url, const char *raw_dat
162
162
 
163
163
  {
164
164
  std::unique_lock<std::mutex> lock(mutex_);
165
- auto &d = transfers_[easy];
165
+ auto [it, inserted] =
166
+ transfers_.try_emplace(easy, Data{
167
+ future,
168
+ loop_,
169
+ stream_callback,
170
+ progress_callback,
171
+ file_stream,
172
+ !stream_callback.is_none() && !is_nobody, // has_stream_callback
173
+ !progress_callback.is_none() && !is_nobody, // has_progress_callback
174
+ {}, // response headers
175
+ std::move(slist_headers),
176
+ std::move(curl_mime_),
177
+ {} // response
178
+ });
179
+ auto &d = it->second;
166
180
  lock.unlock();
167
181
 
168
- d.future = future;
169
- d.request_headers = std::move(slist_headers);
170
- d.curl_mime_ = std::move(curl_mime_);
171
-
172
182
  curl_easy_setopt(easy, CURLOPT_HEADERDATA, &d);
173
183
 
174
184
  if (!is_nobody) {
175
185
  curl_easy_setopt(easy, CURLOPT_WRITEDATA, &d);
176
186
 
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
-
186
- if (!stream_callback.is_none()) {
187
- d.stream_callback = stream_callback;
188
- d.has_stream_callback = true;
189
- }
190
-
191
187
  if (!progress_callback.is_none()) {
192
- d.progress_callback = progress_callback;
193
- d.has_progress_callback = true;
194
-
195
188
  curl_easy_setopt(easy, CURLOPT_XFERINFODATA, &d);
196
189
  curl_easy_setopt(easy, CURLOPT_NOPROGRESS, 0L);
197
190
  curl_easy_setopt(easy, CURLOPT_XFERINFOFUNCTION, &RedC::progress_callback);
198
191
  } else {
199
192
  curl_easy_setopt(easy, CURLOPT_NOPROGRESS, 1L);
200
193
  }
194
+
195
+ if (!file_stream.is_none()) {
196
+ curl_easy_setopt(easy, CURLOPT_UPLOAD, 1L);
197
+ curl_easy_setopt(easy, CURLOPT_READDATA, &d);
198
+ curl_easy_setopt(easy, CURLOPT_READFUNCTION, &RedC::read_callback);
199
+ curl_easy_setopt(easy, CURLOPT_INFILESIZE_LARGE, (curl_off_t)file_size);
200
+ }
201
201
  }
202
202
  }
203
203
 
@@ -218,17 +218,15 @@ void RedC::worker_loop() {
218
218
  CURLMcode res = curl_multi_add_handle(multi_handle_, e);
219
219
  if (res != CURLM_OK) {
220
220
  std::unique_lock<std::mutex> lock(mutex_);
221
- auto it = transfers_.find(e);
222
- if (it != transfers_.end()) {
223
- Data data = std::move(it->second);
224
- transfers_.erase(it);
225
- lock.unlock();
226
- {
227
- acq_gil gil;
228
- call_soon_threadsafe_(nb::cpp_function([data = std::move(data), res]() {
229
- data.future.attr("set_result")(nb::make_tuple(-1, NULL, NULL, (int)res, curl_multi_strerror(res)));
230
- }));
231
- }
221
+ auto node = transfers_.extract(e);
222
+ lock.unlock();
223
+
224
+ if (!node.empty()) {
225
+ Data &data = node.mapped();
226
+ acq_gil gil;
227
+ call_soon_threadsafe_(nb::cpp_function([data = std::move(data), res]() {
228
+ data.future.attr("set_result")(nb::make_tuple(-1, NULL, NULL, (int)res, curl_multi_strerror(res)));
229
+ }));
232
230
  }
233
231
  curl_easy_cleanup(e);
234
232
  }
@@ -248,18 +246,16 @@ void RedC::worker_loop() {
248
246
  while ((msg = curl_multi_info_read(multi_handle_, &msgs_left))) {
249
247
  if (msg->msg == CURLMSG_DONE) {
250
248
  std::unique_lock<std::mutex> lock(mutex_);
251
- auto it = transfers_.find(msg->easy_handle);
252
- if (it != transfers_.end()) {
253
- Data data = std::move(it->second);
254
- transfers_.erase(it);
255
- lock.unlock();
249
+ auto node = transfers_.extract(msg->easy_handle);
250
+ lock.unlock();
256
251
 
257
- {
258
- acq_gil gil;
252
+ if (!node.empty()) {
253
+ Data &data = node.mapped();
254
+ acq_gil gil;
259
255
 
260
- CURLcode res = msg->data.result;
256
+ CURLcode res = msg->data.result;
261
257
 
262
- /*
258
+ /*
263
259
  * Result is allways Tuple:
264
260
 
265
261
  * 0: HTTP response status code.
@@ -274,24 +270,23 @@ void RedC::worker_loop() {
274
270
  *
275
271
  * 4: cURL error message string; can be null
276
272
  */
277
- py_object result;
278
- if (res == CURLE_OK) {
279
- short status_code = 0;
280
- curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &status_code);
281
- result = nb::make_tuple(status_code, py_bytes(data.headers.data(), data.headers.size()),
282
- py_bytes(data.response.data(), data.response.size()), (int)res, NULL);
283
- } else {
284
- result = nb::make_tuple(-1, NULL, NULL, (int)res, curl_easy_strerror(res));
285
- }
286
-
287
- call_soon_threadsafe_(nb::cpp_function([data = std::move(data), result = std::move(result)]() {
288
- data.future.attr("set_result")(std::move(result));
289
- }));
273
+ py_object result;
274
+ if (res == CURLE_OK) {
275
+ short status_code = 0;
276
+ curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &status_code);
277
+ result = nb::make_tuple(status_code, py_bytes(data.headers.data(), data.headers.size()),
278
+ py_bytes(data.response.data(), data.response.size()), (int)res, NULL);
279
+ } else {
280
+ result = nb::make_tuple(-1, NULL, NULL, (int)res, curl_easy_strerror(res));
290
281
  }
291
282
 
292
- curl_multi_remove_handle(multi_handle_, msg->easy_handle);
293
- curl_easy_cleanup(msg->easy_handle);
283
+ call_soon_threadsafe_(nb::cpp_function([data = std::move(data), result = std::move(result)]() {
284
+ data.future.attr("set_result")(std::move(result));
285
+ }));
294
286
  }
287
+
288
+ curl_multi_remove_handle(multi_handle_, msg->easy_handle);
289
+ curl_easy_cleanup(msg->easy_handle);
295
290
  }
296
291
  }
297
292
  }
@@ -300,13 +295,22 @@ void RedC::worker_loop() {
300
295
  void RedC::cleanup() {
301
296
  std::unique_lock<std::mutex> lock(mutex_);
302
297
  acq_gil gil;
303
- for (auto &[easy, data] : transfers_) {
304
- call_soon_threadsafe_(data.future.attr("cancel"));
305
298
 
299
+ std::vector<py_object> futures;
300
+ futures.reserve(transfers_.size());
301
+
302
+ for (auto &[easy, data] : transfers_) {
303
+ futures.push_back(data.future);
306
304
  curl_multi_remove_handle(multi_handle_, easy);
307
305
  curl_easy_cleanup(easy);
308
306
  }
307
+
309
308
  transfers_.clear();
309
+ lock.unlock();
310
+
311
+ for (auto &future : futures) {
312
+ call_soon_threadsafe_(future.attr("cancel"));
313
+ }
310
314
  }
311
315
 
312
316
  void RedC::CHECK_RUNNING() {
@@ -362,8 +366,29 @@ size_t RedC::write_callback(char *data, size_t size, size_t nmemb, Data *clientp
362
366
  return total_size;
363
367
  }
364
368
 
369
+ int redc_tp_traverse(PyObject *self, visitproc visit, void *arg) {
370
+ Py_VISIT(Py_TYPE(self));
371
+
372
+ if (!nb::inst_ready(self))
373
+ return 0;
374
+
375
+ RedC *me = nb::inst_ptr<RedC>(self);
376
+ Py_VISIT(me->loop_.ptr());
377
+ Py_VISIT(me->call_soon_threadsafe_.ptr());
378
+ return 0;
379
+ }
380
+
381
+ int redc_tp_clear(PyObject *self) {
382
+ RedC *c = nb::inst_ptr<RedC>(self);
383
+ c->loop_ = {};
384
+ c->call_soon_threadsafe_ = {};
385
+ return 0;
386
+ }
387
+
388
+ PyType_Slot slots[] = {{Py_tp_traverse, (void *)redc_tp_traverse}, {Py_tp_clear, (void *)redc_tp_clear}, {0, 0}};
389
+
365
390
  NB_MODULE(redc_ext, m) {
366
- nb::class_<RedC>(m, "RedC")
391
+ nb::class_<RedC>(m, "RedC", nb::type_slots(slots))
367
392
  .def(nb::init<const long &>())
368
393
  .def("is_running", &RedC::is_running)
369
394
  .def("request", &RedC::request, arg("method"), arg("url"), arg("raw_data") = "", arg("file_stream") = nb::none(),
@@ -33,6 +33,14 @@ bool isNullOrEmpty(const char *str) {
33
33
  }
34
34
 
35
35
  struct Data {
36
+ Data() = default;
37
+
38
+ Data(const Data &) = delete;
39
+ Data &operator=(const Data &) = delete;
40
+
41
+ Data(Data &&) = default;
42
+ Data &operator=(Data &&) = default;
43
+
36
44
  py_object future;
37
45
  py_object loop;
38
46
  py_object stream_callback{nb::none()};
@@ -90,6 +98,9 @@ class RedC {
90
98
  static size_t progress_callback(Data *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal,
91
99
  curl_off_t ulnow);
92
100
  static size_t write_callback(char *data, size_t size, size_t nmemb, Data *clientp);
101
+
102
+ friend int redc_tp_traverse(PyObject *, visitproc, void *);
103
+ friend int redc_tp_clear(PyObject *);
93
104
  };
94
105
 
95
106
  #endif // REDC_H
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
File without changes
File without changes