Skip to content

Commit 608fefd

Browse files
authored
chore: PyO3 Migration from v0.20.* to 0.24.* (#1168)
* chore: update versions * wip fix syntax from migration * fixes task_locals clone issue on server.rs ;___; * fix function_info clone issue * `?is_none` to `is_ok` * impl Clone for FunctionInfo * fix typo * getattr => get_item * chore: apply rust-fix suggestions * add default value to `pydict#get` * manual clone impl for WebSocketConnector * `is_ok` to `is_ok_and` to handle `None` * chore: `cargo fmt` * add explicit GIL lifetime management for add_route function
1 parent a13dea4 commit 608fefd

File tree

18 files changed

+649
-458
lines changed

18 files changed

+649
-458
lines changed

Cargo.lock

Lines changed: 481 additions & 356 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@ name = "robyn"
1414
crate-type = ["cdylib", "rlib"]
1515

1616
[dependencies]
17-
pyo3 = { version = "0.20.0", features = ["extension-module"] }
18-
pyo3-asyncio = { version="0.20.0" , features = ["attributes", "tokio-runtime"] }
19-
pyo3-log = "0.8.4"
20-
tokio = { version = "1.26.0", features = ["full"] }
17+
pyo3 = { version = "0.24.2", features = ["extension-module", "py-clone"]}
18+
pyo3-async-runtimes = { version = "0.24", features = ["tokio-runtime"] }
19+
pyo3-async-runtimes-macros = { version = "0.24" }
20+
pyo3-log = "0.12.3"
21+
tokio = { version = "1.40", features = ["full"] }
2122
dashmap = "5.4.3"
2223
anyhow = "1.0.69"
2324
actix = "0.13.4"
@@ -31,7 +32,7 @@ matchit = "0.7.3"
3132
socket2 = { version = "0.5.1", features = ["all"] }
3233
uuid = { version = "1.3.0", features = ["serde", "v4"] }
3334
log = "0.4.17"
34-
pythonize = "0.20.0"
35+
pythonize = "0.24"
3536
serde = "1.0.187"
3637
serde_json = "1.0.109"
3738
once_cell = "1.8.0"

integration_tests/base_routes.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ async def message(ws: WebSocketConnector, msg: str, global_dependencies) -> str:
6060
elif state == 1:
6161
resp = "Whooo??"
6262
elif state == 2:
63-
await ws.async_broadcast(ws.query_params.get("one"))
64-
ws.sync_send_to(websocket_id, ws.query_params.get("two"))
63+
await ws.async_broadcast(ws.query_params.get("one", None))
64+
ws.sync_send_to(websocket_id, ws.query_params.get("two", None))
6565
resp = "*chika* *chika* Slim Shady."
6666
elif state == 3:
6767
ws.close()

src/executors/mod.rs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::sync::Arc;
88
use anyhow::Result;
99
use log::debug;
1010
use pyo3::prelude::*;
11-
use pyo3_asyncio::TaskLocals;
11+
use pyo3_async_runtimes::TaskLocals;
1212

1313
use crate::types::{
1414
function_info::FunctionInfo, request::Request, response::Response, MiddlewareReturn,
@@ -19,20 +19,22 @@ fn get_function_output<'a, T>(
1919
function: &'a FunctionInfo,
2020
py: Python<'a>,
2121
function_args: &T,
22-
) -> Result<&'a PyAny, PyErr>
22+
) -> Result<pyo3::Bound<'a, pyo3::PyAny>, PyErr>
2323
where
2424
T: ToPyObject,
2525
{
26-
let handler = function.handler.as_ref(py);
27-
let kwargs = function.kwargs.as_ref(py);
26+
let handler = function.handler.bind(py).downcast()?;
27+
let kwargs = function.kwargs.bind(py);
2828
let function_args = function_args.to_object(py);
2929
debug!("Function args: {:?}", function_args);
3030

3131
match function.number_of_params {
3232
0 => handler.call0(),
3333
1 => {
34-
if kwargs.get_item("global_dependencies")?.is_some()
35-
|| kwargs.get_item("router_dependencies")?.is_some()
34+
if pyo3::types::PyDictMethods::get_item(kwargs, "global_dependencies")
35+
.is_ok_and(|it| !it.is_none())
36+
|| pyo3::types::PyDictMethods::get_item(kwargs, "router_dependencies")
37+
.is_ok_and(|it| !it.is_none())
3638
// these are reserved keywords
3739
{
3840
handler.call((), Some(kwargs))
@@ -57,7 +59,7 @@ where
5759
{
5860
if function.is_async {
5961
let output: Py<PyAny> = Python::with_gil(|py| {
60-
pyo3_asyncio::tokio::into_future(get_function_output(function, py, input)?)
62+
pyo3_async_runtimes::tokio::into_future(get_function_output(function, py, input)?)
6163
})?
6264
.await?;
6365

@@ -88,7 +90,7 @@ pub async fn execute_http_function(
8890
if function.is_async {
8991
let output = Python::with_gil(|py| {
9092
let function_output = get_function_output(function, py, request)?;
91-
pyo3_asyncio::tokio::into_future(function_output)
93+
pyo3_async_runtimes::tokio::into_future(function_output)
9294
})?
9395
.await?;
9496

@@ -108,9 +110,9 @@ pub async fn execute_startup_handler(
108110
if function.is_async {
109111
debug!("Startup event handler async");
110112
Python::with_gil(|py| {
111-
pyo3_asyncio::into_future_with_locals(
113+
pyo3_async_runtimes::into_future_with_locals(
112114
task_locals,
113-
function.handler.as_ref(py).call0()?,
115+
function.handler.bind(py).call0()?,
114116
)
115117
})?
116118
.await?;

src/executors/web_socket_executors.rs

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
use actix::prelude::*;
2-
use actix::AsyncContext;
3-
use actix_web_actors::ws;
1+
use actix::{ActorFutureExt, AsyncContext, WrapFuture};
2+
use actix_web_actors::ws::WebsocketContext;
43
use pyo3::prelude::*;
5-
use pyo3_asyncio::TaskLocals;
4+
use pyo3_async_runtimes::TaskLocals;
65

76
use crate::types::function_info::FunctionInfo;
87
use crate::websockets::WebSocketConnector;
@@ -12,42 +11,54 @@ fn get_function_output<'a>(
1211
fn_msg: Option<String>,
1312
py: Python<'a>,
1413
ws: &WebSocketConnector,
15-
) -> Result<&'a PyAny, PyErr> {
16-
let handler = function.handler.as_ref(py);
14+
) -> Result<pyo3::Bound<'a, pyo3::PyAny>, PyErr> {
15+
let handler = function.handler.bind(py).downcast()?;
1716

1817
// this makes the request object accessible across every route
1918

20-
let args = function.args.as_ref(py);
21-
let kwargs = function.kwargs.as_ref(py);
19+
let args = function.args.bind(py).downcast()?;
20+
let kwargs = function.kwargs.bind(py).downcast()?;
2221

2322
match function.number_of_params {
2423
0 => handler.call0(),
2524
1 => {
26-
if args.get_item("ws")?.is_some() {
25+
if pyo3::types::PyDictMethods::get_item(args, "ws").is_ok_and(|it| !it.is_none()) {
2726
handler.call1((ws.clone(),))
28-
} else if args.get_item("msg")?.is_some() {
27+
} else if pyo3::types::PyDictMethods::get_item(args, "msg")
28+
.is_ok_and(|it| !it.is_none())
29+
{
2930
handler.call1((fn_msg.unwrap_or_default(),))
3031
} else {
3132
handler.call((), Some(kwargs))
3233
}
3334
}
3435
2 => {
35-
if args.get_item("ws")?.is_some() && args.get_item("msg")?.is_some() {
36+
if pyo3::types::PyDictMethods::get_item(args, "ws").is_ok_and(|it| !it.is_none())
37+
&& pyo3::types::PyDictMethods::get_item(args, "msg").is_ok_and(|it| !it.is_none())
38+
{
3639
handler.call1((ws.clone(), fn_msg.unwrap_or_default()))
37-
} else if args.get_item("ws")?.is_some() {
40+
} else if pyo3::types::PyDictMethods::get_item(args, "ws").is_ok_and(|it| !it.is_none())
41+
{
3842
handler.call((ws.clone(),), Some(kwargs))
39-
} else if args.get_item("msg")?.is_some() {
43+
} else if pyo3::types::PyDictMethods::get_item(args, "msg")
44+
.is_ok_and(|it| !it.is_none())
45+
{
4046
handler.call((fn_msg.unwrap_or_default(),), Some(kwargs))
4147
} else {
4248
handler.call((), Some(kwargs))
4349
}
4450
}
4551
3 => {
46-
if args.get_item("ws")?.is_some() && args.get_item("msg")?.is_some() {
52+
if pyo3::types::PyDictMethods::get_item(args, "ws").is_ok_and(|it| !it.is_none())
53+
&& pyo3::types::PyDictMethods::get_item(args, "msg").is_ok_and(|it| !it.is_none())
54+
{
4755
handler.call((ws.clone(), fn_msg.unwrap_or_default()), Some(kwargs))
48-
} else if args.get_item("ws")?.is_some() {
56+
} else if pyo3::types::PyDictMethods::get_item(args, "ws").is_ok_and(|it| !it.is_none())
57+
{
4958
handler.call((ws.clone(),), Some(kwargs))
50-
} else if args.get_item("msg")?.is_some() {
59+
} else if pyo3::types::PyDictMethods::get_item(args, "msg")
60+
.is_ok_and(|it| !it.is_none())
61+
{
5162
handler.call((fn_msg.unwrap_or_default(),), Some(kwargs))
5263
} else {
5364
handler.call((), Some(kwargs))
@@ -61,13 +72,13 @@ pub fn execute_ws_function(
6172
function: &FunctionInfo,
6273
text: Option<String>,
6374
task_locals: &TaskLocals,
64-
ctx: &mut ws::WebsocketContext<WebSocketConnector>,
75+
ctx: &mut WebsocketContext<WebSocketConnector>,
6576
ws: &WebSocketConnector,
6677
// add number of params here
6778
) {
6879
if function.is_async {
6980
let fut = Python::with_gil(|py| {
70-
pyo3_asyncio::into_future_with_locals(
81+
pyo3_async_runtimes::into_future_with_locals(
7182
task_locals,
7283
get_function_output(function, text, py, ws).unwrap(),
7384
)
@@ -84,7 +95,7 @@ pub fn execute_ws_function(
8495
Python::with_gil(|py| {
8596
if let Some(op) = get_function_output(function, text, py, ws)
8697
.unwrap()
87-
.extract::<Option<&str>>()
98+
.extract::<Option<String>>()
8899
.unwrap()
89100
{
90101
ctx.text(op);

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ fn get_version() -> String {
2929
}
3030

3131
#[pymodule]
32-
pub fn robyn(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
32+
pub fn robyn(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
3333
// the pymodule class/function to make the rustPyFunctions available
3434
m.add_function(wrap_pyfunction!(get_version, m)?)?;
3535

src/routers/const_router.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::types::HttpMethod;
1010
use anyhow::Context;
1111
use log::debug;
1212
use matchit::Router as MatchItRouter;
13-
use pyo3::types::PyAny;
13+
use pyo3::{Bound, Python};
1414

1515
use anyhow::{Error, Result};
1616

@@ -25,12 +25,13 @@ pub struct ConstRouter {
2525

2626
impl Router<Response, HttpMethod> for ConstRouter {
2727
/// Doesn't allow query params/body/etc as variables cannot be "memoized"/"const"ified
28-
fn add_route(
28+
fn add_route<'py>(
2929
&self,
30+
_py: Python,
3031
route_type: &HttpMethod,
3132
route: &str,
3233
function: FunctionInfo,
33-
event_loop: Option<&PyAny>,
34+
event_loop: Option<Bound<'py, pyo3::PyAny>>,
3435
) -> Result<(), Error> {
3536
let table = self
3637
.routes
@@ -42,7 +43,7 @@ impl Router<Response, HttpMethod> for ConstRouter {
4243
let event_loop =
4344
event_loop.context("Event loop must be provided to add a route to the const router")?;
4445

45-
pyo3_asyncio::tokio::run_until_complete(event_loop, async move {
46+
pyo3_async_runtimes::tokio::run_until_complete(event_loop, async move {
4647
let output = execute_http_function(&Request::default(), &function)
4748
.await
4849
.unwrap();

src/routers/http_router.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use parking_lot::RwLock;
2+
use pyo3::{Bound, Python};
23
use std::collections::HashMap;
34

45
use matchit::Router as MatchItRouter;
5-
use pyo3::types::PyAny;
66

77
use anyhow::{Context, Result};
88

@@ -18,12 +18,13 @@ pub struct HttpRouter {
1818
}
1919

2020
impl Router<(FunctionInfo, HashMap<String, String>), HttpMethod> for HttpRouter {
21-
fn add_route(
21+
fn add_route<'py>(
2222
&self,
23+
_py: Python,
2324
route_type: &HttpMethod,
2425
route: &str,
2526
function: FunctionInfo,
26-
_event_loop: Option<&PyAny>,
27+
_event_loop: Option<Bound<'py, pyo3::PyAny>>,
2728
) -> Result<()> {
2829
let table = self.routes.get(route_type).context("No relevant map")?;
2930

@@ -47,7 +48,9 @@ impl Router<(FunctionInfo, HashMap<String, String>), HttpMethod> for HttpRouter
4748
route_params.insert(key.to_string(), value.to_string());
4849
}
4950

50-
Some((res.value.to_owned(), route_params))
51+
let function_info = Python::with_gil(|_| res.value.to_owned());
52+
53+
Some((function_info, route_params))
5154
}
5255
}
5356

src/routers/middleware_router.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::sync::RwLock;
33

44
use anyhow::{Context, Error, Result};
55
use matchit::Router as MatchItRouter;
6-
use pyo3::types::PyAny;
6+
use pyo3::{Bound, Python};
77

88
use crate::routers::Router;
99
use crate::types::function_info::{FunctionInfo, MiddlewareType};
@@ -17,12 +17,13 @@ pub struct MiddlewareRouter {
1717
}
1818

1919
impl Router<(FunctionInfo, HashMap<String, String>), MiddlewareType> for MiddlewareRouter {
20-
fn add_route(
20+
fn add_route<'py>(
2121
&self,
22+
_py: Python,
2223
route_type: &MiddlewareType,
2324
route: &str,
2425
function: FunctionInfo,
25-
_event_loop: Option<&PyAny>,
26+
_event_loop: Option<Bound<'py, pyo3::PyAny>>,
2627
) -> Result<(), Error> {
2728
let table = self.routes.get(route_type).context("No relevant map")?;
2829

@@ -45,7 +46,9 @@ impl Router<(FunctionInfo, HashMap<String, String>), MiddlewareType> for Middlew
4546
route_params.insert(key.to_string(), value.to_string());
4647
}
4748

48-
Some((res.value.to_owned(), route_params))
49+
let function_info = Python::with_gil(|_| res.value.to_owned());
50+
51+
Some((function_info, route_params))
4952
}
5053
}
5154

src/routers/mod.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use anyhow::Result;
2-
use pyo3::PyAny;
2+
use pyo3::{Bound, Python};
33

44
use crate::types::function_info::FunctionInfo;
55

@@ -11,12 +11,13 @@ pub mod web_socket_router;
1111
pub trait Router<T, U> {
1212
/// Checks if the functions is an async function
1313
/// Inserts them in the router according to their nature(CoRoutine/SyncFunction)
14-
fn add_route(
14+
fn add_route<'py>(
1515
&self,
16+
py: Python,
1617
route_type: &U,
1718
route: &str,
1819
function: FunctionInfo,
19-
event_loop: Option<&PyAny>,
20+
event_loop: Option<Bound<'py, pyo3::PyAny>>,
2021
) -> Result<()>;
2122

2223
/// Retrieve the correct function from the previously inserted routes

0 commit comments

Comments
 (0)