Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 12 additions & 13 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ http-rewriter = { git = "ssh://git@github.com/platformatic/http-rewriter" }
# Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix
napi = { version = "3", default-features = false, features = ["napi4", "tokio_rt", "async"], optional = true }
napi-derive = { version = "3", optional = true }
pyo3 = { version = "0.25.1", features = ["experimental-async"] }
pyo3-async-runtimes = { version = "0.25.0", features = ["tokio-runtime"] }
pyo3 = { version = "0.26.0", features = ["experimental-async"] }
pyo3-async-runtimes = { version = "0.26.0", features = ["tokio-runtime"] }
thiserror = "2.0.12"
tokio = { version = "1.45.1", features = ["full"] }
libc = "0.2"
Expand Down
12 changes: 6 additions & 6 deletions src/asgi/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -527,8 +527,8 @@ mod tests {

#[test]
fn test_http_connection_scope_into_pyobject() {
pyo3::prepare_freethreaded_python();
Python::with_gil(|py| {
Python::initialize();
Python::attach(|py| {
let scope = HttpConnectionScope {
http_version: HttpVersion::V1_1,
method: HttpMethod::Get,
Expand Down Expand Up @@ -577,8 +577,8 @@ mod tests {

#[test]
fn test_http_receive_message_into_pyobject() {
pyo3::prepare_freethreaded_python();
Python::with_gil(|py| {
Python::initialize();
Python::attach(|py| {
let message = HttpReceiveMessage::Request {
body: vec![1, 2, 3],
more_body: true,
Expand All @@ -596,8 +596,8 @@ mod tests {

#[test]
fn test_http_send_message_from_pyobject() {
pyo3::prepare_freethreaded_python();
Python::with_gil(|py| {
Python::initialize();
Python::attach(|py| {
let dict = PyDict::new(py);
dict.set_item("type", "http.response.start").unwrap();
dict.set_item("status", 200).unwrap();
Expand Down
2 changes: 1 addition & 1 deletion src/asgi/http_method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ mod tests {

#[test]
fn test_http_method_pyobject_conversion() {
Python::with_gil(|py| {
Python::attach(|py| {
let tests = vec![
(HttpMethod::Get, "GET"),
(HttpMethod::Post, "POST"),
Expand Down
2 changes: 1 addition & 1 deletion src/asgi/http_version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ mod tests {

#[test]
fn test_http_version_pyobject_conversion() {
Python::with_gil(|py| {
Python::attach(|py| {
let tests = vec![
(HttpVersion::V1_0, "1.0"),
(HttpVersion::V1_1, "1.1"),
Expand Down
2 changes: 1 addition & 1 deletion src/asgi/info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ mod tests {

#[test]
fn test_asgi_info_pyobject_conversion() {
Python::with_gil(|py| {
Python::attach(|py| {
let asgi_info = AsgiInfo::new("3.0", "2.5");

// Convert AsgiInfo to PyObject
Expand Down
10 changes: 5 additions & 5 deletions src/asgi/lifespan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ mod tests {

#[test]
fn test_lifespan_scope_into_pyobject() {
Python::with_gil(|py| {
Python::attach(|py| {
let lifespan_scope = LifespanScope { state: None };
let py_obj = lifespan_scope.into_pyobject(py).unwrap();
assert_eq!(
Expand All @@ -129,7 +129,7 @@ mod tests {

#[test]
fn test_lifespan_receive_message_into_pyobject() {
Python::with_gil(|py| {
Python::attach(|py| {
let message = LifespanReceiveMessage::LifespanStartup;
let py_obj = message.into_pyobject(py).unwrap();
assert_eq!(
Expand All @@ -148,7 +148,7 @@ mod tests {

#[test]
fn test_lifespan_send_message_from_pyobject() {
Python::with_gil(|py| {
Python::attach(|py| {
let dict = PyDict::new(py);
dict.set_item("type", "lifespan.shutdown.complete").unwrap();
let message: LifespanSendMessage = dict.extract().unwrap();
Expand All @@ -163,7 +163,7 @@ mod tests {

#[test]
fn test_lifespan_send_message_from_pyobject_error_cases() {
Python::with_gil(|py| {
Python::attach(|py| {
// Test missing 'type' key
let dict = PyDict::new(py);
let result: Result<LifespanSendMessage, _> = dict.extract();
Expand Down Expand Up @@ -261,7 +261,7 @@ mod tests {

#[test]
fn test_lifespan_scope_with_populated_state() {
Python::with_gil(|py| {
Python::attach(|py| {
// Create a state dictionary with some data
let state_dict = PyDict::new(py);
state_dict.set_item("initialized", true).unwrap();
Expand Down
20 changes: 10 additions & 10 deletions src/asgi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,20 +62,20 @@ pub use websocket::{

/// Handle to a shared Python event loop
pub struct EventLoopHandle {
event_loop: PyObject,
event_loop: Py<PyAny>,
}

impl EventLoopHandle {
/// Get the Python event loop object
pub fn event_loop(&self) -> &PyObject {
pub fn event_loop(&self) -> &Py<PyAny> {
&self.event_loop
}
}

impl Drop for EventLoopHandle {
fn drop(&mut self) {
// Stop the Python event loop when the last handle is dropped
Python::with_gil(|py| {
Python::attach(|py| {
if let Err(e) = self.event_loop.bind(py).call_method0("stop") {
eprintln!("Failed to stop Python event loop: {e}");
}
Expand Down Expand Up @@ -111,10 +111,10 @@ fn create_event_loop_handle() -> Result<EventLoopHandle, HandlerError> {
ensure_python_symbols_global();

// Initialize Python if not already initialized
pyo3::prepare_freethreaded_python();
Python::initialize();

// Create event loop
let event_loop = Python::with_gil(|py| -> Result<PyObject, HandlerError> {
let event_loop = Python::attach(|py| -> Result<Py<PyAny>, HandlerError> {
let asyncio = py.import("asyncio")?;
let event_loop = asyncio.call_method0("new_event_loop")?;
let event_loop_py = event_loop.unbind();
Expand All @@ -139,7 +139,7 @@ pub struct Asgi {
// Shared Python event loop handle
event_loop_handle: Arc<EventLoopHandle>,
// ASGI app function
app_function: PyObject,
app_function: Py<PyAny>,
}

unsafe impl Send for Asgi {}
Expand All @@ -160,7 +160,7 @@ impl Asgi {
let event_loop_handle = ensure_python_event_loop()?;

// Load Python app
let app_function = Python::with_gil(|py| -> Result<PyObject, HandlerError> {
let app_function = Python::attach(|py| -> Result<Py<PyAny>, HandlerError> {
// Load and compile Python module
let entrypoint = docroot
.join(format!("{}.py", target.file))
Expand Down Expand Up @@ -241,7 +241,7 @@ impl Handler for Asgi {
tokio::spawn(collect_response_messages(tx_receiver, response_tx));

// Submit the ASGI app call to Python event loop
Python::with_gil(|py| {
Python::attach(|py| {
let scope_py = scope.into_pyobject(py)?;
let coro = self
.app_function
Expand Down Expand Up @@ -361,8 +361,8 @@ fn setup_python_paths(py: Python, docroot: &Path) -> PyResult<()> {
}

/// Start a Python thread that runs the event loop forever
fn start_python_event_loop_thread(event_loop: PyObject) {
Python::with_gil(|py| {
fn start_python_event_loop_thread(event_loop: Py<PyAny>) {
Python::attach(|py| {
// Set the event loop for this thread and run it
let asyncio = py.import("asyncio")?;
asyncio.call_method1("set_event_loop", (event_loop.bind(py),))?;
Expand Down
6 changes: 3 additions & 3 deletions src/asgi/receiver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,23 +50,23 @@ impl Receiver {
ReceiverType::Http(rx) => {
let message = rx.lock().await.recv().await;
if let Some(msg) = message {
Python::with_gil(|py| Ok(msg.into_pyobject(py)?.unbind()))
Python::attach(|py| Ok(msg.into_pyobject(py)?.unbind()))
} else {
Err(PyValueError::new_err("No message received"))
}
}
ReceiverType::WebSocket(rx) => {
let message = rx.lock().await.recv().await;
if let Some(msg) = message {
Python::with_gil(|py| Ok(msg.into_pyobject(py)?.unbind()))
Python::attach(|py| Ok(msg.into_pyobject(py)?.unbind()))
} else {
Err(PyValueError::new_err("No message received"))
}
}
ReceiverType::Lifespan(rx) => {
let message = rx.lock().await.recv().await;
if let Some(msg) = message {
Python::with_gil(|py| Ok(msg.into_pyobject(py)?.unbind()))
Python::attach(|py| Ok(msg.into_pyobject(py)?.unbind()))
} else {
Err(PyValueError::new_err("No message received"))
}
Expand Down
6 changes: 3 additions & 3 deletions src/asgi/sender.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,12 @@ impl Sender {

#[pymethods]
impl Sender {
async fn __call__(&mut self, args: Py<PyDict>) -> PyResult<PyObject> {
async fn __call__(&mut self, args: Py<PyDict>) -> PyResult<Py<PyAny>> {
// Create acknowledgment channel
let (ack_tx, ack_rx) = oneshot::channel::<()>();

// Send message with acknowledgment channel
let send_result: PyResult<()> = Python::with_gil(|py| {
let send_result: PyResult<()> = Python::attach(|py| {
let args_dict = args.bind(py);
match &self.0 {
SenderType::Http(tx) => {
Expand Down Expand Up @@ -95,7 +95,7 @@ impl Sender {

// Wait for acknowledgment
match ack_rx.await {
Ok(()) => Python::with_gil(|py| Ok(py.None())),
Ok(()) => Python::attach(|py| Ok(py.None())),
Err(_) => Err(PyValueError::new_err("message not acknowledged")),
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/asgi/websocket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ mod tests {

#[test]
fn test_websocket_connection_scope_into_pyobject() {
Python::with_gil(|py| {
Python::attach(|py| {
let scope = WebSocketConnectionScope {
http_version: HttpVersion::V2_0,
scheme: "ws".to_string(),
Expand Down Expand Up @@ -353,7 +353,7 @@ mod tests {

#[test]
fn test_websocket_receive_message_into_pyobject() {
Python::with_gil(|py| {
Python::attach(|py| {
let connect_msg = WebSocketReceiveMessage::Connect;
let dict = connect_msg.into_pyobject(py).unwrap();
assert_eq!(
Expand Down Expand Up @@ -398,7 +398,7 @@ mod tests {

#[test]
fn test_websocket_send_message_from_pyobject() {
Python::with_gil(|py| {
Python::attach(|py| {
let dict = PyDict::new(py);
dict.set_item("type", "websocket.accept").unwrap();
dict.set_item("subprotocol", "chat").unwrap();
Expand Down
Loading