Skip to content

Commit 6247c7d

Browse files
committed
Introspection: add typing.final on final classes
1 parent d15a14a commit 6247c7d

File tree

12 files changed

+85
-26
lines changed

12 files changed

+85
-26
lines changed

pyo3-introspection/src/introspection.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,13 @@ fn convert_members<'a>(
124124
chunks_by_parent,
125125
)?);
126126
}
127-
Chunk::Class { name, id } => {
128-
classes.push(convert_class(id, name, chunks_by_id, chunks_by_parent)?)
129-
}
127+
Chunk::Class { name, id, is_final } => classes.push(convert_class(
128+
id,
129+
name,
130+
*is_final,
131+
chunks_by_id,
132+
chunks_by_parent,
133+
)?),
130134
Chunk::Function {
131135
name,
132136
id: _,
@@ -168,6 +172,7 @@ fn convert_members<'a>(
168172
fn convert_class(
169173
id: &str,
170174
name: &str,
175+
is_final: bool,
171176
chunks_by_id: &HashMap<&str, &Chunk>,
172177
chunks_by_parent: &HashMap<&str, Vec<&Chunk>>,
173178
) -> Result<Class> {
@@ -188,6 +193,7 @@ fn convert_class(
188193
name: name.into(),
189194
methods,
190195
attributes,
196+
is_final,
191197
})
192198
}
193199

@@ -409,6 +415,8 @@ enum Chunk {
409415
Class {
410416
id: String,
411417
name: String,
418+
#[serde(default, rename = "final")]
419+
is_final: bool,
412420
},
413421
Function {
414422
#[serde(default)]

pyo3-introspection/src/model.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pub struct Class {
1313
pub name: String,
1414
pub methods: Vec<Function>,
1515
pub attributes: Vec<Attribute>,
16+
pub is_final: bool,
1617
}
1718

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

pyo3-introspection/src/stubs.rs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,21 @@ fn module_stubs(module: &Module, parents: &[&str]) -> String {
110110
}
111111

112112
fn class_stubs(class: &Class, imports: &Imports) -> String {
113-
let mut buffer = format!("class {}:", class.name);
113+
let mut buffer = String::new();
114+
if class.is_final {
115+
buffer.push('@');
116+
imports.serialize_type_hint(
117+
&TypeHintExpr::Attribute {
118+
module: "typing".into(),
119+
attr: "final".into(),
120+
},
121+
&mut buffer,
122+
);
123+
buffer.push('\n');
124+
}
125+
buffer.push_str("class ");
126+
buffer.push_str(&class.name);
127+
buffer.push(':');
114128
if class.methods.is_empty() && class.attributes.is_empty() {
115129
buffer.push_str(" ...");
116130
return buffer;
@@ -410,6 +424,12 @@ impl ElementsUsedInAnnotations {
410424
}
411425

412426
fn walk_class(&mut self, class: &Class) {
427+
if class.is_final {
428+
self.module_members
429+
.entry("typing".into())
430+
.or_default()
431+
.insert("final".into());
432+
}
413433
for method in &class.methods {
414434
self.walk_function(method);
415435
}
@@ -605,6 +625,7 @@ mod tests {
605625
name: "A".into(),
606626
methods: Vec::new(),
607627
attributes: Vec::new(),
628+
is_final: true,
608629
}],
609630
functions: vec![Function {
610631
name: String::new(),
@@ -628,7 +649,8 @@ mod tests {
628649
&[
629650
"from _typeshed import Incomplete",
630651
"from bat import A as A2",
631-
"from foo import A as A3, B"
652+
"from foo import A as A3, B",
653+
"from typing import final"
632654
]
633655
);
634656
let mut output = String::new();

pyo3-macros-backend/src/introspection.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ pub fn class_introspection_code(
6161
pyo3_crate_path: &PyO3CratePath,
6262
ident: &Ident,
6363
name: &str,
64+
is_final: bool,
6465
) -> TokenStream {
6566
IntrospectionNode::Map(
6667
[
@@ -70,6 +71,7 @@ pub fn class_introspection_code(
7071
IntrospectionNode::IntrospectionId(Some(ident_to_type(ident))),
7172
),
7273
("name", IntrospectionNode::String(name.into())),
74+
("final", IntrospectionNode::Bool(is_final)),
7375
]
7476
.into(),
7577
)

pyo3-macros-backend/src/pyclass.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2664,7 +2664,12 @@ impl<'a> PyClassImplsBuilder<'a> {
26642664
let Ctx { pyo3_path, .. } = ctx;
26652665
let name = get_class_python_name(self.cls, self.attr).to_string();
26662666
let ident = self.cls;
2667-
let static_introspection = class_introspection_code(pyo3_path, ident, &name);
2667+
let static_introspection = class_introspection_code(
2668+
pyo3_path,
2669+
ident,
2670+
&name,
2671+
self.attr.options.subclass.is_none(),
2672+
);
26682673
let introspection_id = introspection_id_const();
26692674
quote! {
26702675
#static_introspection

pytests/src/lib.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ mod pyo3_pytests {
2525
#[pymodule_export]
2626
use {
2727
comparisons::comparisons, consts::consts, enums::enums, pyclasses::pyclasses,
28-
pyfunctions::pyfunctions,
28+
pyfunctions::pyfunctions, subclassing::subclassing,
2929
};
3030

3131
// Inserting to sys.modules allows importing submodules nicely from Python
@@ -43,7 +43,6 @@ mod pyo3_pytests {
4343
m.add_wrapped(wrap_pymodule!(othermod::othermod))?;
4444
m.add_wrapped(wrap_pymodule!(path::path))?;
4545
m.add_wrapped(wrap_pymodule!(sequence::sequence))?;
46-
m.add_wrapped(wrap_pymodule!(subclassing::subclassing))?;
4746

4847
// Inserting to sys.modules allows importing submodules nicely from Python
4948
// e.g. import pyo3_pytests.buf_and_str as bas

pytests/src/subclassing.rs

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,22 @@
22
33
use pyo3::prelude::*;
44

5-
#[pyclass(subclass)]
6-
pub struct Subclassable {}
5+
#[pymodule(gil_used = false)]
6+
pub mod subclassing {
7+
use pyo3::prelude::*;
78

8-
#[pymethods]
9-
impl Subclassable {
10-
#[new]
11-
fn new() -> Self {
12-
Subclassable {}
13-
}
9+
#[pyclass(subclass)]
10+
pub struct Subclassable {}
1411

15-
fn __str__(&self) -> &'static str {
16-
"Subclassable"
17-
}
18-
}
12+
#[pymethods]
13+
impl Subclassable {
14+
#[new]
15+
fn new() -> Self {
16+
Subclassable {}
17+
}
1918

20-
#[pymodule(gil_used = false)]
21-
pub fn subclassing(m: &Bound<'_, PyModule>) -> PyResult<()> {
22-
m.add_class::<Subclassable>()?;
23-
Ok(())
19+
fn __str__(&self) -> &'static str {
20+
"Subclassable"
21+
}
22+
}
2423
}

pytests/stubs/comparisons.pyi

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
1+
from typing import final
2+
3+
@final
14
class Eq:
25
def __eq__(self, /, other: Eq) -> bool: ...
36
def __ne__(self, /, other: Eq) -> bool: ...
47
def __new__(cls, /, value: int) -> None: ...
58

9+
@final
610
class EqDefaultNe:
711
def __eq__(self, /, other: EqDefaultNe) -> bool: ...
812
def __new__(cls, /, value: int) -> None: ...
913

14+
@final
1015
class EqDerived:
1116
def __eq__(self, /, other: EqDerived) -> bool: ...
1217
def __ne__(self, /, other: EqDerived) -> bool: ...
1318
def __new__(cls, /, value: int) -> None: ...
1419

20+
@final
1521
class Ordered:
1622
def __eq__(self, /, other: Ordered) -> bool: ...
1723
def __ge__(self, /, other: Ordered) -> bool: ...
@@ -21,6 +27,7 @@ class Ordered:
2127
def __ne__(self, /, other: Ordered) -> bool: ...
2228
def __new__(cls, /, value: int) -> None: ...
2329

30+
@final
2431
class OrderedDefaultNe:
2532
def __eq__(self, /, other: OrderedDefaultNe) -> bool: ...
2633
def __ge__(self, /, other: OrderedDefaultNe) -> bool: ...
@@ -29,6 +36,7 @@ class OrderedDefaultNe:
2936
def __lt__(self, /, other: OrderedDefaultNe) -> bool: ...
3037
def __new__(cls, /, value: int) -> None: ...
3138

39+
@final
3240
class OrderedDerived:
3341
def __eq__(self, /, other: OrderedDerived) -> bool: ...
3442
def __ge__(self, /, other: OrderedDerived) -> bool: ...
@@ -40,6 +48,7 @@ class OrderedDerived:
4048
def __new__(cls, /, value: int) -> None: ...
4149
def __str__(self, /) -> str: ...
4250

51+
@final
4352
class OrderedRichCmp:
4453
def __eq__(self, /, other: OrderedRichCmp) -> bool: ...
4554
def __ge__(self, /, other: OrderedRichCmp) -> bool: ...

pytests/stubs/consts.pyi

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
from typing import Final
1+
from typing import Final, final
22

33
PI: Final[float]
44
SIMPLE: Final = "SIMPLE"
55

6+
@final
67
class ClassWithConst:
78
INSTANCE: Final[ClassWithConst]

pytests/stubs/enums.pyi

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
from typing import final
2+
13
class ComplexEnum: ...
24
class MixedComplexEnum: ...
35

6+
@final
47
class SimpleEnum:
58
def __eq__(self, /, other: SimpleEnum | int) -> bool: ...
69
def __ne__(self, /, other: SimpleEnum | int) -> bool: ...

0 commit comments

Comments
 (0)