Skip to content

Commit 185e6b3

Browse files
authored
Stub generation & introspection: emit async keyword for async functions (#5731)
* Introspection: emit async keyword for async functions * Add newsfragment & fix formatting * fix * Remove unnecessary feature gate
1 parent 7fb7dde commit 185e6b3

File tree

12 files changed

+56
-2
lines changed

12 files changed

+56
-2
lines changed

newsfragments/5731.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Stub generation & introspection: emit `async` keyword for async functions.

noxfile.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1120,7 +1120,7 @@ def test_introspection(session: nox.Session):
11201120
"-m",
11211121
"./pytests/Cargo.toml",
11221122
"--features",
1123-
"experimental-inspect",
1123+
"experimental-async,experimental-inspect",
11241124
*options,
11251125
)
11261126
# We look for the built library

pyo3-introspection/src/introspection.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,11 @@ fn convert_members<'a>(
151151
arguments,
152152
parent: _,
153153
decorators,
154+
is_async,
154155
returns,
155-
} => functions.push(convert_function(name, arguments, decorators, returns)),
156+
} => functions.push(convert_function(
157+
name, arguments, decorators, returns, *is_async,
158+
)),
156159
Chunk::Attribute {
157160
name,
158161
id: _,
@@ -225,6 +228,7 @@ fn convert_function(
225228
arguments: &ChunkArguments,
226229
decorators: &[ChunkExpr],
227230
returns: &Option<ChunkTypeHint>,
231+
is_async: bool,
228232
) -> Function {
229233
Function {
230234
name: name.into(),
@@ -243,6 +247,7 @@ fn convert_function(
243247
.map(convert_variable_length_argument),
244248
},
245249
returns: returns.as_ref().map(convert_type_hint),
250+
is_async,
246251
}
247252
}
248253

@@ -475,6 +480,8 @@ enum Chunk {
475480
decorators: Vec<ChunkExpr>,
476481
#[serde(default)]
477482
returns: Option<ChunkTypeHint>,
483+
#[serde(default, rename = "async")]
484+
is_async: bool,
478485
},
479486
Attribute {
480487
#[serde(default)]

pyo3-introspection/src/model.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub struct Function {
2626
pub arguments: Arguments,
2727
/// return type
2828
pub returns: Option<TypeHint>,
29+
pub is_async: bool,
2930
}
3031

3132
#[derive(Debug, Eq, PartialEq, Clone, Hash)]

pyo3-introspection/src/stubs.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ fn module_stubs(module: &Module, parents: &[&str]) -> String {
8585
}),
8686
attr: "Incomplete".into(),
8787
})),
88+
is_async: false,
8889
},
8990
&imports,
9091
));
@@ -187,6 +188,10 @@ fn function_stubs(function: &Function, imports: &Imports) -> String {
187188
imports.serialize_expr(decorator, &mut buffer);
188189
buffer.push('\n');
189190
}
191+
if function.is_async {
192+
buffer.push_str("async ");
193+
}
194+
190195
buffer.push_str("def ");
191196
buffer.push_str(&function.name);
192197
buffer.push('(');
@@ -633,6 +638,7 @@ mod tests {
633638
}),
634639
},
635640
returns: Some(TypeHint::Plain("list[str]".into())),
641+
is_async: false,
636642
};
637643
assert_eq!(
638644
"def func(posonly, /, arg, *varargs, karg: str, **kwarg: str) -> list[str]: ...",
@@ -665,13 +671,35 @@ mod tests {
665671
kwarg: None,
666672
},
667673
returns: None,
674+
is_async: false,
668675
};
669676
assert_eq!(
670677
"def afunc(posonly=1, /, arg=True, *, karg: str = \"foo\"): ...",
671678
function_stubs(&function, &Imports::default())
672679
)
673680
}
674681

682+
#[test]
683+
fn test_function_async() {
684+
let function = Function {
685+
name: "foo".into(),
686+
decorators: Vec::new(),
687+
arguments: Arguments {
688+
positional_only_arguments: Vec::new(),
689+
arguments: Vec::new(),
690+
vararg: None,
691+
keyword_only_arguments: Vec::new(),
692+
kwarg: None,
693+
},
694+
returns: None,
695+
is_async: true,
696+
};
697+
assert_eq!(
698+
"async def foo(): ...",
699+
function_stubs(&function, &Imports::default())
700+
)
701+
}
702+
675703
#[test]
676704
fn test_import() {
677705
let big_type = Expr::Subscript {
@@ -776,6 +804,7 @@ mod tests {
776804
kwarg: None,
777805
},
778806
returns: Some(TypeHint::Ast(big_type.clone())),
807+
is_async: false,
779808
}],
780809
attributes: Vec::new(),
781810
incomplete: true,

pyo3-macros-backend/src/introspection.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,13 @@ pub fn function_introspection_code(
9292
first_argument: Option<&'static str>,
9393
returns: ReturnType,
9494
decorators: impl IntoIterator<Item = PythonTypeHint>,
95+
is_async: bool,
9596
parent: Option<&Type>,
9697
) -> TokenStream {
9798
let mut desc = HashMap::from([
9899
("type", IntrospectionNode::String("function".into())),
99100
("name", IntrospectionNode::String(name.into())),
101+
("async", IntrospectionNode::Bool(is_async)),
100102
(
101103
"arguments",
102104
arguments_introspection_data(signature, first_argument, parent),

pyo3-macros-backend/src/pyclass.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1610,6 +1610,7 @@ fn generate_protocol_slot(
16101610
Some("self"),
16111611
parse_quote!(-> #returns),
16121612
[],
1613+
spec.asyncness.is_some(),
16131614
Some(cls),
16141615
)
16151616
})
@@ -1954,6 +1955,7 @@ fn descriptors_to_items(
19541955
Some("self"),
19551956
parse_quote!(-> #return_type),
19561957
vec![PythonTypeHint::builtin("property")],
1958+
false,
19571959
Some(&parse_quote!(#cls)),
19581960
));
19591961
}
@@ -2006,6 +2008,7 @@ fn descriptors_to_items(
20062008
),
20072009
"setter",
20082010
)],
2011+
false,
20092012
Some(&parse_quote!(#cls)),
20102013
));
20112014
}

pyo3-macros-backend/src/pyfunction.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,7 @@ pub fn impl_wrap_pyfunction(
396396
None,
397397
func.sig.output.clone(),
398398
empty(),
399+
func.sig.asyncness.is_some(),
399400
None,
400401
);
401402
#[cfg(not(feature = "experimental-inspect"))]

pyo3-macros-backend/src/pyimpl.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,7 @@ fn method_introspection_code(spec: &FnSpec<'_>, parent: &syn::Type, ctx: &Ctx) -
463463
first_argument,
464464
return_type,
465465
decorators,
466+
spec.asyncness.is_some(),
466467
Some(parent),
467468
)
468469
}

pytests/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ publish = false
88
rust-version = "1.83"
99

1010
[features]
11+
experimental-async = ["pyo3/experimental-async"]
1112
experimental-inspect = ["pyo3/experimental-inspect"]
1213

1314
[dependencies]

0 commit comments

Comments
 (0)