From 3191fcb33a637ab7030effbd3a8da689703779af Mon Sep 17 00:00:00 2001 From: jessekrubin Date: Fri, 20 Dec 2024 17:13:28 -0800 Subject: [PATCH 1/4] STREEEEEEEEEEEEEMING --- Cargo.lock | 16 +++ crates/ryo3-reqwest/Cargo.toml | 15 ++- crates/ryo3-reqwest/src/async_client.rs | 167 ++++++++++++++++++------ 3 files changed, 160 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2e2efac..890beda 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1495,6 +1495,7 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", "windows-registry", ] @@ -1788,6 +1789,8 @@ name = "ryo3-reqwest" version = "0.0.23" dependencies = [ "bytes", + "futures-core", + "futures-util", "jiter", "pyo3", "pyo3-async-runtimes", @@ -2519,6 +2522,19 @@ version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.76" diff --git a/crates/ryo3-reqwest/Cargo.toml b/crates/ryo3-reqwest/Cargo.toml index 92303cf..5c4e066 100644 --- a/crates/ryo3-reqwest/Cargo.toml +++ b/crates/ryo3-reqwest/Cargo.toml @@ -14,11 +14,24 @@ pyo3 = { workspace = true, features = ["experimental-inspect", "experimental-asy pyo3-async-runtimes = { workspace = true, features = ["attributes", "tokio-runtime"] } tracing.workspace = true tokio.workspace = true -reqwest = { version = "0.12.9", features = ["blocking", "brotli", "cookies", "gzip", "zstd", "charset", "http2", "macos-system-configuration", "rustls-tls-native-roots"], default-features = false} +reqwest = { version = "0.12.9", features = [ + "blocking", + "brotli", + "charset", + "cookies", + "gzip", + "http2", + "macos-system-configuration", + "rustls-tls-native-roots", + "stream", + "zstd" +], default-features = false} bytes = { workspace = true } jiter.workspace = true ryo3-bytes.workspace = true ryo3-url.workspace = true +futures-core = "0.3.31" +futures-util = "0.3.31" [lints] workspace = true diff --git a/crates/ryo3-reqwest/src/async_client.rs b/crates/ryo3-reqwest/src/async_client.rs index 72ec472..fbd1d30 100644 --- a/crates/ryo3-reqwest/src/async_client.rs +++ b/crates/ryo3-reqwest/src/async_client.rs @@ -1,12 +1,15 @@ use pyo3::prelude::*; +use std::sync::Arc; use crate::errors::map_reqwest_err; use crate::pyo3_bytes::Pyo3JsonBytes; -use pyo3::exceptions::PyValueError; +use futures_util::StreamExt; +use pyo3::exceptions::{PyStopAsyncIteration, PyValueError}; use pyo3::types::PyDict; use reqwest::StatusCode; use ryo3_bytes::Pyo3Bytes; use ryo3_url::PyUrl; +// for `.next()` #[pyclass] #[pyo3(name = "AsyncClient")] @@ -22,27 +25,26 @@ pub struct RyAsyncResponse { // cookies: reqwest::cookie::CookieJar, // version: Option, url: reqwest::Url, + content_length: Option, // body: Option, res: Option, } -// impl RyAsyncResponse { -// async fn read_body_async(&mut self) -> Result<(), PyErr> { -// if self.body.is_none() { -// let res = self -// .res -// .take() -// .ok_or_else(|| PyValueError::new_err("Response already consumed"))?; -// let b = res -// .bytes() -// .await -// .map_err(|e| PyValueError::new_err(format!("{e}")))?; -// self.body = Some(b); -// } -// Ok(()) -// } -// } +impl From for RyAsyncResponse { + fn from(res: reqwest::Response) -> Self { + Self { + status_code: res.status(), + headers: res.headers().clone(), + // cookies: res.cookies().clone(), + // version: res.version(), + url: res.url().clone(), + content_length: res.content_length(), + // body: None, + res: Some(res), + } + } +} #[pymethods] impl RyAsyncResponse { #[getter] @@ -73,6 +75,11 @@ impl RyAsyncResponse { Ok(pydict) } + #[getter] + fn content_length(&self) -> Option { + self.content_length + } + fn bytes<'py>(&'py mut self, py: Python<'py>) -> PyResult> { let response = self .res @@ -110,6 +117,26 @@ impl RyAsyncResponse { }) } + fn bytes_stream<'py>(&'py mut self, py: Python<'py>) -> PyResult { + let response = self + .res + .take() + .ok_or(PyValueError::new_err("Response already consumed"))?; + + // HOLY SHIT THIS TOOK A LOT OF TRIAL AND ERROR + let stream = response.bytes_stream(); + let stream = Box::pin(stream); + Ok(RyAsyncResponseIter { + stream: Arc::new(tokio::sync::Mutex::new(stream)), + }) + // let s = RyAsyncResponseIter { response }; + // Ok(s) + + // pyo3_async_runtimes::tokio::future_into_py(py, async move { + // Ok(response.bytes_stream()) + // }) + } + fn __str__(&self) -> String { format!("Response: {}", self.status_code) } @@ -119,6 +146,36 @@ impl RyAsyncResponse { } } +#[pyclass] +pub struct RyAsyncResponseIter { + stream: Arc< + tokio::sync::Mutex< + std::pin::Pin< + Box> + Send>, + >, + >, + >, +} + +#[pymethods] +impl RyAsyncResponseIter { + fn __aiter__(this: PyRef) -> PyRef { + this + } + + fn __anext__<'py>(&self, py: Python<'py>) -> PyResult> { + let stream = self.stream.clone(); + pyo3_async_runtimes::tokio::future_into_py(py, async move { + let mut guard = stream.lock().await; + match guard.as_mut().next().await { + Some(Ok(bytes)) => Ok(Some(Pyo3Bytes::from(bytes))), + Some(Err(e)) => Err(map_reqwest_err(e)), + None => Err(PyStopAsyncIteration::new_err("stream exhausted")), // No more items, return None to end iteration + } + }) + } +} + #[pymethods] impl RyAsyncClient { #[new] @@ -130,17 +187,10 @@ impl RyAsyncClient { fn get<'py>(&'py mut self, py: Python<'py>, url: &str) -> PyResult> { let response_future = self.0.get(url).send(); pyo3_async_runtimes::tokio::future_into_py(py, async move { - let response = response_future + response_future .await - .map_err(|e| PyValueError::new_err(format!("{e}")))?; - let r = RyAsyncResponse { - status_code: response.status(), - headers: response.headers().clone(), - url: response.url().clone(), - // body: None, - res: Some(response), - }; - Ok(r) + .map(RyAsyncResponse::from) + .map_err(map_reqwest_err) }) } @@ -152,17 +202,60 @@ impl RyAsyncClient { ) -> PyResult> { let response_future = self.0.post(url).body(body.to_vec()).send(); pyo3_async_runtimes::tokio::future_into_py(py, async move { - let response = response_future + response_future .await - .map_err(|e| PyValueError::new_err(format!("{e}")))?; - let r = RyAsyncResponse { - status_code: response.status(), - headers: response.headers().clone(), - url: response.url().clone(), - // body: None, - res: Some(response), - }; - Ok(r) + .map(RyAsyncResponse::from) + .map_err(map_reqwest_err) + }) + } + + fn put<'py>( + &'py mut self, + py: Python<'py>, + url: &str, + body: &[u8], + ) -> PyResult> { + let response_future = self.0.put(url).body(body.to_vec()).send(); + pyo3_async_runtimes::tokio::future_into_py(py, async move { + response_future + .await + .map(RyAsyncResponse::from) + .map_err(map_reqwest_err) + }) + } + + fn patch<'py>( + &'py mut self, + py: Python<'py>, + url: &str, + body: &[u8], + ) -> PyResult> { + let response_future = self.0.patch(url).body(body.to_vec()).send(); + pyo3_async_runtimes::tokio::future_into_py(py, async move { + response_future + .await + .map(RyAsyncResponse::from) + .map_err(map_reqwest_err) + }) + } + + fn delete<'py>(&'py mut self, py: Python<'py>, url: &str) -> PyResult> { + let response_future = self.0.delete(url).send(); + pyo3_async_runtimes::tokio::future_into_py(py, async move { + response_future + .await + .map(RyAsyncResponse::from) + .map_err(map_reqwest_err) + }) + } + + fn head<'py>(&'py mut self, py: Python<'py>, url: &str) -> PyResult> { + let response_future = self.0.head(url).send(); + pyo3_async_runtimes::tokio::future_into_py(py, async move { + response_future + .await + .map(RyAsyncResponse::from) + .map_err(map_reqwest_err) }) } } From 10fa775a28b01c61a8848740d34425a9fb7c8ff1 Mon Sep 17 00:00:00 2001 From: jessekrubin Date: Fri, 20 Dec 2024 19:17:39 -0800 Subject: [PATCH 2/4] async client clippy fixes --- crates/ryo3-reqwest/src/async_client.rs | 28 ++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/crates/ryo3-reqwest/src/async_client.rs b/crates/ryo3-reqwest/src/async_client.rs index fbd1d30..b5e41b9 100644 --- a/crates/ryo3-reqwest/src/async_client.rs +++ b/crates/ryo3-reqwest/src/async_client.rs @@ -1,15 +1,17 @@ -use pyo3::prelude::*; -use std::sync::Arc; - use crate::errors::map_reqwest_err; use crate::pyo3_bytes::Pyo3JsonBytes; +use bytes::Bytes; +use futures_core::Stream; use futures_util::StreamExt; use pyo3::exceptions::{PyStopAsyncIteration, PyValueError}; +use pyo3::prelude::*; use pyo3::types::PyDict; use reqwest::StatusCode; use ryo3_bytes::Pyo3Bytes; use ryo3_url::PyUrl; -// for `.next()` +use std::pin::Pin; +use std::sync::Arc; +use tokio::sync::Mutex; #[pyclass] #[pyo3(name = "AsyncClient")] @@ -117,7 +119,7 @@ impl RyAsyncResponse { }) } - fn bytes_stream<'py>(&'py mut self, py: Python<'py>) -> PyResult { + fn bytes_stream(&mut self) -> PyResult { let response = self .res .take() @@ -127,7 +129,7 @@ impl RyAsyncResponse { let stream = response.bytes_stream(); let stream = Box::pin(stream); Ok(RyAsyncResponseIter { - stream: Arc::new(tokio::sync::Mutex::new(stream)), + stream: Arc::new(Mutex::new(stream)), }) // let s = RyAsyncResponseIter { response }; // Ok(s) @@ -146,15 +148,12 @@ impl RyAsyncResponse { } } +// clippy says this is too long and complicated to just sit in the struct def +type AsyncResponseStreamInner = + Arc> + Send>>>>; #[pyclass] pub struct RyAsyncResponseIter { - stream: Arc< - tokio::sync::Mutex< - std::pin::Pin< - Box> + Send>, - >, - >, - >, + stream: AsyncResponseStreamInner, } #[pymethods] @@ -170,7 +169,8 @@ impl RyAsyncResponseIter { match guard.as_mut().next().await { Some(Ok(bytes)) => Ok(Some(Pyo3Bytes::from(bytes))), Some(Err(e)) => Err(map_reqwest_err(e)), - None => Err(PyStopAsyncIteration::new_err("stream exhausted")), // No more items, return None to end iteration + // I totally forgot that this was a thing and that I couldn't just return None + None => Err(PyStopAsyncIteration::new_err("response-stream-fin")), } }) } From cedeb933e9b38a7ba7d09357bd8d6b24956f9816 Mon Sep 17 00:00:00 2001 From: jessekrubin Date: Fri, 20 Dec 2024 23:25:07 -0800 Subject: [PATCH 3/4] reqwest more stuff --- crates/ryo3-reqwest/src/async_client.rs | 30 +++--- crates/ryo3-reqwest/src/blocking.rs | 135 +++++++++++++----------- crates/ryo3-reqwest/src/lib.rs | 2 - crates/ryo3-reqwest/src/pyo3_bytes.rs | 36 +------ 4 files changed, 92 insertions(+), 111 deletions(-) diff --git a/crates/ryo3-reqwest/src/async_client.rs b/crates/ryo3-reqwest/src/async_client.rs index b5e41b9..3dcf16c 100644 --- a/crates/ryo3-reqwest/src/async_client.rs +++ b/crates/ryo3-reqwest/src/async_client.rs @@ -21,16 +21,19 @@ pub struct RyAsyncClient(reqwest::Client); #[pyo3(name = "AsyncResponse")] #[derive(Debug)] pub struct RyAsyncResponse { - // Store the response in an Option so we can take ownership later. + /// The actual response which will be consumed when read + res: Option, + + // ======================================================================== + /// das status code status_code: StatusCode, + /// das headers headers: reqwest::header::HeaderMap, - // cookies: reqwest::cookie::CookieJar, - // version: Option, + /// das url url: reqwest::Url, + /// das content length -- if it exists (tho it might not and/or be + /// different if the response is compressed) content_length: Option, - - // body: Option, - res: Option, } impl From for RyAsyncResponse { @@ -71,7 +74,6 @@ impl RyAsyncResponse { .to_str() .map(String::from) .map_err(|e| PyValueError::new_err(format!("{e}")))?; - // .to_str()?.to_string(); pydict.set_item(k, v)?; } Ok(pydict) @@ -119,6 +121,7 @@ impl RyAsyncResponse { }) } + /// Return a response consuming async iterator over the response body fn bytes_stream(&mut self) -> PyResult { let response = self .res @@ -131,12 +134,6 @@ impl RyAsyncResponse { Ok(RyAsyncResponseIter { stream: Arc::new(Mutex::new(stream)), }) - // let s = RyAsyncResponseIter { response }; - // Ok(s) - - // pyo3_async_runtimes::tokio::future_into_py(py, async move { - // Ok(response.bytes_stream()) - // }) } fn __str__(&self) -> String { @@ -148,6 +145,12 @@ impl RyAsyncResponse { } } +// This whole response iterator was a difficult thing to figure out. +// +// NOTE: I (jesse) am pretty proud of this. I was struggling to get the +// async-iterator thingy to work bc rust + async is quite hard, but +// after lots and lots and lots of trial and error this works! +// // clippy says this is too long and complicated to just sit in the struct def type AsyncResponseStreamInner = Arc> + Send>>>>; @@ -183,7 +186,6 @@ impl RyAsyncClient { Self(reqwest::Client::new()) } - // self.request(Method::GET, url) fn get<'py>(&'py mut self, py: Python<'py>, url: &str) -> PyResult> { let response_future = self.0.get(url).send(); pyo3_async_runtimes::tokio::future_into_py(py, async move { diff --git a/crates/ryo3-reqwest/src/blocking.rs b/crates/ryo3-reqwest/src/blocking.rs index a520cf8..d65afec 100644 --- a/crates/ryo3-reqwest/src/blocking.rs +++ b/crates/ryo3-reqwest/src/blocking.rs @@ -12,20 +12,37 @@ use ryo3_url::PyUrl; #[pyclass] #[pyo3(name = "Response")] #[derive(Debug)] -pub struct RyResponse { +pub struct RyBlockingResponse { // Store the response in an Option so we can take ownership later. + /// The actual response which will be consumed when read + res: Option, + + /// The body stored as bytes in the ob + body: Option, + // ======================================================================== + /// das status code status_code: StatusCode, + /// das headers headers: reqwest::header::HeaderMap, - // cookies: reqwest::cookie::CookieJar, - // version: Option, + /// das url url: reqwest::Url, - - body: Option, - - res: Option, + /// das content length -- if it exists (tho it might not and/or be + /// different if the response is compressed) + content_length: Option, } - -impl RyResponse { +impl From for RyBlockingResponse { + fn from(res: reqwest::blocking::Response) -> Self { + Self { + status_code: res.status(), + headers: res.headers().clone(), + url: res.url().clone(), + content_length: res.content_length(), + body: None, + res: Some(res), + } + } +} +impl RyBlockingResponse { fn read_body(&mut self) -> PyResult<()> { if let Some(_b) = self.body.as_ref() { Ok(()) @@ -39,13 +56,12 @@ impl RyResponse { .map_err(|e| PyValueError::new_err(format!("{e}")))?; self.body = Some(b); Ok(()) - // Ok(&*b) } } } #[pymethods] -impl RyResponse { +impl RyBlockingResponse { #[getter] fn status_code(&self) -> PyResult { let res = self @@ -60,24 +76,6 @@ impl RyResponse { "Something went wrong.... this should not happen", ))?; Ok(PyBytes::new(slf.py(), b)) - - // match slf.body.as_ref() { - // Some(b) => Ok(b.to_vec()), - // None => { - // // Take ownership of the response, leaving None in place. - // let res = slf - // .res - // .take() - // .ok_or_else(|| PyValueError::new_err("Response already consumed"))?; - // - // // Now we have full ownership of res, so we can call text() without error. - // let b = res - // .bytes() - // .map_err(|e| PyValueError::new_err(format!("{e}")))?; - // // return the b - // Ok(b.to_vec()) - // } - // } } #[getter] #[pyo3(name = "url")] @@ -96,7 +94,6 @@ impl RyResponse { .to_str() .map(String::from) .map_err(|e| PyValueError::new_err(format!("{e}")))?; - // .to_str()?.to_string(); pydict.set_item(k, v)?; } Ok(pydict) @@ -119,21 +116,15 @@ impl RyResponse { format!("Response: {}", self.status_code) } - // ) -> PyResult> { - fn json(mut slf: PyRefMut<'_, Self>) -> PyResult> { slf.read_body()?; let parse_builder = PythonParse { allow_inf_nan: true, - cache_mode: ::jiter::StringCacheMode::All, - partial_mode: ::jiter::PartialMode::Off, + cache_mode: jiter::StringCacheMode::All, + partial_mode: jiter::PartialMode::Off, catch_duplicate_keys: false, - float_mode: ::jiter::FloatMode::Float, - // cache_mode = StringCacheMode::All, - // partial_mode = PartialMode::Off, - // catch_duplicate_keys = false, - // float_mode = FloatMode::Float + float_mode: jiter::FloatMode::Float, }; let b = slf.body.as_ref().ok_or(PyValueError::new_err( "Something went wrong.... this should not happen", @@ -155,32 +146,54 @@ impl RyClient { Self(reqwest::blocking::Client::new()) } - // self.request(Method::GET, url) - - fn get(&self, url: &str) -> PyResult { - let response = self - .0 + fn get(&self, url: &str) -> PyResult { + self.0 .get(url) .send() - .map_err(|e| PyValueError::new_err(format!("{e}")))?; + .map(RyBlockingResponse::from) + .map_err(|e| PyValueError::new_err(format!("{e}"))) + } - let url = response.url().clone(); - let headers = response.headers().clone(); - let status_code = response.status(); + fn post(&self, url: &str, body: &str) -> PyResult { + self.0 + .post(url) + .body(body.to_string()) + .send() + .map(RyBlockingResponse::from) + .map_err(|e| PyValueError::new_err(format!("{e}"))) + } - Ok(RyResponse { - status_code, - headers, - url, - body: None, + fn put(&self, url: &str, body: &str) -> PyResult { + self.0 + .put(url) + .body(body.to_string()) + .send() + .map(RyBlockingResponse::from) + .map_err(|e| PyValueError::new_err(format!("{e}"))) + } + + fn patch(&self, url: &str, body: &str) -> PyResult { + self.0 + .patch(url) + .body(body.to_string()) + .send() + .map(RyBlockingResponse::from) + .map_err(|e| PyValueError::new_err(format!("{e}"))) + } - res: Some(response), - }) + fn delete(&self, url: &str) -> PyResult { + self.0 + .delete(url) + .send() + .map(RyBlockingResponse::from) + .map_err(|e| PyValueError::new_err(format!("{e}"))) } -} -// pub fn pymod_add(py: Python<'_>, m: &PyModule) -> PyResult<()> { -// m.add_class::()?; -// m.add_class::()?; -// Ok(()) -// } + fn head(&self, url: &str) -> PyResult { + self.0 + .head(url) + .send() + .map(RyBlockingResponse::from) + .map_err(|e| PyValueError::new_err(format!("{e}"))) + } +} diff --git a/crates/ryo3-reqwest/src/lib.rs b/crates/ryo3-reqwest/src/lib.rs index c373b46..c43fa12 100644 --- a/crates/ryo3-reqwest/src/lib.rs +++ b/crates/ryo3-reqwest/src/lib.rs @@ -11,7 +11,5 @@ use pyo3::prelude::*; pub fn pymod_add(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; - // m.add_class::()?; - // m.add_function(wrap_pyfunction!(self::which, m)?)?; Ok(()) } diff --git a/crates/ryo3-reqwest/src/pyo3_bytes.rs b/crates/ryo3-reqwest/src/pyo3_bytes.rs index d27cb01..a648f99 100644 --- a/crates/ryo3-reqwest/src/pyo3_bytes.rs +++ b/crates/ryo3-reqwest/src/pyo3_bytes.rs @@ -1,30 +1,6 @@ use bytes::Bytes; use jiter::{map_json_error, PythonParse}; use pyo3::prelude::*; -// -// pub(crate) struct Pyo3Bytes(pub Bytes); -// -// impl Pyo3Bytes { -// pub fn new(buf: Bytes) -> Self { -// Self(buf) -// } -// } -// impl From for Pyo3Bytes { -// fn from(value: Bytes) -> Self { -// Self::new(value) -// } -// } -// -// impl<'py> IntoPyObject<'py> for Pyo3Bytes { -// type Target = PyBytes; -// type Output = Bound<'py, Self::Target>; -// type Error = PyErr; -// -// fn into_pyobject(self, py: Python<'py>) -> Result { -// Ok(PyBytes::new(py, &self.0[..])) -// } -// } - pub(crate) struct Pyo3JsonBytes(pub Bytes); impl Pyo3JsonBytes { @@ -46,23 +22,15 @@ impl<'py> IntoPyObject<'py> for Pyo3JsonBytes { fn into_pyobject(self, py: Python<'py>) -> Result { let b_u8 = &self.0[..]; - let parse_builder = PythonParse { + let parser = PythonParse { allow_inf_nan: true, cache_mode: ::jiter::StringCacheMode::All, partial_mode: ::jiter::PartialMode::Off, catch_duplicate_keys: false, float_mode: ::jiter::FloatMode::Float, - // cache_mode = StringCacheMode::All, - // partial_mode = PartialMode::Off, - // catch_duplicate_keys = false, - // float_mode = FloatMode::Float }; - // let b = slf.body.as_ref().unwrap(); - // let r = - - parse_builder + parser .python_parse(py, b_u8) .map_err(|e| map_json_error(b_u8, &e)) - // r } } From 17aedc3a688cd8267ed29d78aefaa2784828b329 Mon Sep 17 00:00:00 2001 From: jessekrubin Date: Fri, 20 Dec 2024 23:50:46 -0800 Subject: [PATCH 4/4] fix formatting and req' --- Cargo.toml | 11 +++++-- crates/_ryo3-dev/src/lib.rs | 2 -- crates/ryo3-reqwest/src/async_client.rs | 1 + crates/ryo3-reqwest/src/blocking.rs | 7 +++++ python/ry/__init__.py | 5 +++- python/ry/ryo3/dirs.pyi | 38 +++++++++++++++++++++++++ scripts/gen.py | 5 ++-- tests/dirs/__init__.py | 0 8 files changed, 61 insertions(+), 8 deletions(-) create mode 100644 python/ry/ryo3/dirs.pyi create mode 100644 tests/dirs/__init__.py diff --git a/Cargo.toml b/Cargo.toml index 27ad496..e1dacb2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,10 +25,15 @@ opt-level = 0 [profile.release] opt-level = 3 -strip = true # strip debug symbols +strip = true debug = false -lto = "thin" # not sure if this should be 'fat' -#lto = "fat" +# __FAT_VS_THIN_LTO__? +# tbd if this ought to be fat or thin, on one hand, 'thin' compiles MUCH faster +# but on the other hand, I am an american... +# there does not seem to be a noticeable difference in the performance tho +# at the time of writing this (2024-12-20) the `fat` binary is 12.5mb and +# the `thin` binary is 12.6mb (who's thin now!?). +lto = "thin" # or "fat" # release fat profile [profile.bench] diff --git a/crates/_ryo3-dev/src/lib.rs b/crates/_ryo3-dev/src/lib.rs index 9efad35..a23f2e3 100644 --- a/crates/_ryo3-dev/src/lib.rs +++ b/crates/_ryo3-dev/src/lib.rs @@ -24,8 +24,6 @@ pub mod sp; pub fn pymod_add(m: &Bound<'_, PyModule>) -> PyResult<()> { anystr::pymod_add(m)?; sp::pymod_add(m)?; - - println!("adding req"); ryo3_reqwest::pymod_add(m)?; Ok(()) } diff --git a/crates/ryo3-reqwest/src/async_client.rs b/crates/ryo3-reqwest/src/async_client.rs index 3dcf16c..8f447ef 100644 --- a/crates/ryo3-reqwest/src/async_client.rs +++ b/crates/ryo3-reqwest/src/async_client.rs @@ -79,6 +79,7 @@ impl RyAsyncResponse { Ok(pydict) } + /// Return the content length of the response, if it is known or `None`. #[getter] fn content_length(&self) -> Option { self.content_length diff --git a/crates/ryo3-reqwest/src/blocking.rs b/crates/ryo3-reqwest/src/blocking.rs index d65afec..ae24fc6 100644 --- a/crates/ryo3-reqwest/src/blocking.rs +++ b/crates/ryo3-reqwest/src/blocking.rs @@ -98,6 +98,13 @@ impl RyBlockingResponse { } Ok(pydict) } + + /// Return the content length of the response, if it is known or `None`. + #[getter] + fn content_length(&self) -> Option { + self.content_length + } + fn text(mut slf: PyRefMut<'_, Self>) -> PyResult { slf.read_body()?; let b = slf.body.as_ref().ok_or(PyValueError::new_err( diff --git a/python/ry/__init__.py b/python/ry/__init__.py index 2ca7388..7c201ba 100644 --- a/python/ry/__init__.py +++ b/python/ry/__init__.py @@ -1,4 +1,7 @@ -"""dev entry point""" +"""ry = rust + python + +`ry` is a kitchen-sink collection of wrappers for well vetted and popular rust crates +""" from ry import ryo3 from ry.ryo3 import ( diff --git a/python/ry/ryo3/dirs.pyi b/python/ry/ryo3/dirs.pyi new file mode 100644 index 0000000..3219d70 --- /dev/null +++ b/python/ry/ryo3/dirs.pyi @@ -0,0 +1,38 @@ +def audio() -> str | None: ... +def audio_dir() -> str | None: ... +def cache() -> str | None: ... +def cache_dir() -> str | None: ... +def config() -> str | None: ... +def config_dir() -> str | None: ... +def config_local() -> str | None: ... +def config_local_dir() -> str | None: ... +def data() -> str | None: ... +def data_dir() -> str | None: ... +def data_local() -> str | None: ... +def data_local_dir() -> str | None: ... +def desktop() -> str | None: ... +def desktop_dir() -> str | None: ... +def document() -> str | None: ... +def document_dir() -> str | None: ... +def download() -> str | None: ... +def download_dir() -> str | None: ... +def executable() -> str | None: ... +def executable_dir() -> str | None: ... +def font() -> str | None: ... +def font_dir() -> str | None: ... +def home() -> str | None: ... +def home_dir() -> str | None: ... +def picture() -> str | None: ... +def picture_dir() -> str | None: ... +def preference() -> str | None: ... +def preference_dir() -> str | None: ... +def public() -> str | None: ... +def public_dir() -> str | None: ... +def runtime() -> str | None: ... +def runtime_dir() -> str | None: ... +def state() -> str | None: ... +def state_dir() -> str | None: ... +def template() -> str | None: ... +def template_dir() -> str | None: ... +def video() -> str | None: ... +def video_dir() -> str | None: ... diff --git a/scripts/gen.py b/scripts/gen.py index 28975be..7b7ea78 100644 --- a/scripts/gen.py +++ b/scripts/gen.py @@ -2,7 +2,7 @@ import sys -from ry import dev as ryo3 +import ry.ryo3 as ryo3 def eprint(*args, **kwargs): @@ -53,10 +53,11 @@ def sort_all(strings: list[str]): # test it try: exec(init_string) # noqa: S102 + sys.stdout.buffer.write(init_string.encode("utf-8")) except Exception as e: + print(init_string) eprint(e) raise e from None - sys.stdout.buffer.write(init_string.encode("utf-8")) if __name__ == "__main__": diff --git a/tests/dirs/__init__.py b/tests/dirs/__init__.py new file mode 100644 index 0000000..e69de29