pure/
lib.rs

1#![allow(deprecated)]
2
3mod custom_exceptions;
4mod manual_overloading;
5mod manual_submit;
6mod overloading;
7mod overriding;
8mod rust_type_marker;
9mod skip_stub_type_test;
10
11use custom_exceptions::*;
12use manual_overloading::*;
13use manual_submit::*;
14use overloading::*;
15use overriding::*;
16use rust_type_marker::*;
17use skip_stub_type_test::*;
18
19#[cfg_attr(target_os = "macos", doc = include_str!("../../../README.md"))]
20mod readme {}
21
22use ahash::RandomState;
23use pyo3::{prelude::*, types::*};
24use pyo3_stub_gen::{define_stub_info_gatherer, derive::*, module_doc, module_variable};
25use rust_decimal::Decimal;
26use std::{collections::HashMap, path::PathBuf};
27
28/// Returns the sum of two numbers as a string.
29#[gen_stub_pyfunction]
30#[pyfunction]
31fn sum(v: Vec<u32>) -> u32 {
32    v.iter().sum()
33}
34
35#[gen_stub_pyfunction]
36#[pyfunction]
37fn read_dict(dict: HashMap<usize, HashMap<usize, usize>>) {
38    for (k, v) in dict {
39        for (k2, v2) in v {
40            println!("{k} {k2} {v2}");
41        }
42    }
43}
44
45#[gen_stub_pyfunction]
46#[pyfunction]
47fn create_dict(n: usize) -> HashMap<usize, Vec<usize>> {
48    let mut dict = HashMap::new();
49    for i in 0..n {
50        dict.insert(i, (0..i).collect());
51    }
52    dict
53}
54
55/// Add two decimal numbers with high precision
56#[gen_stub_pyfunction]
57#[pyfunction]
58fn add_decimals(a: Decimal, b: Decimal) -> Decimal {
59    a + b
60}
61
62#[gen_stub_pyclass]
63#[pyclass(extends=PyDate)]
64struct MyDate;
65
66#[gen_stub_pyclass]
67#[pyclass(subclass)]
68#[derive(Debug)]
69struct A {
70    #[gen_stub(default = A::default().x)]
71    #[pyo3(get, set)]
72    x: usize,
73
74    #[pyo3(get)]
75    y: usize,
76}
77
78impl Default for A {
79    fn default() -> Self {
80        Self { x: 2, y: 10 }
81    }
82}
83
84#[gen_stub_pymethods]
85#[pymethods]
86impl A {
87    /// This is a constructor of :class:`A`.
88    #[new]
89    fn new(x: usize) -> Self {
90        Self { x, y: 10 }
91    }
92    /// class attribute NUM1
93    #[classattr]
94    #[pyo3(name = "NUM")]
95    const NUM1: usize = 2;
96
97    /// deprecated class attribute NUM3 (will show warning)
98    #[deprecated(since = "1.0.0", note = "This constant is deprecated")]
99    #[classattr]
100    const NUM3: usize = 3;
101    /// class attribute NUM2
102    #[expect(non_snake_case)]
103    #[classattr]
104    fn NUM2() -> usize {
105        2
106    }
107    #[classmethod]
108    fn classmethod_test1(cls: &Bound<'_, PyType>) {
109        _ = cls;
110    }
111
112    #[deprecated(since = "1.0.0", note = "This classmethod is deprecated")]
113    #[classmethod]
114    fn deprecated_classmethod(cls: &Bound<'_, PyType>) {
115        _ = cls;
116    }
117
118    #[classmethod]
119    fn classmethod_test2(_: &Bound<'_, PyType>) {}
120
121    fn show_x(&self) {
122        println!("x = {}", self.x);
123    }
124
125    fn ref_test<'a>(&self, x: Bound<'a, PyDict>) -> Bound<'a, PyDict> {
126        x
127    }
128
129    async fn async_get_x(&self) -> usize {
130        self.x
131    }
132
133    #[gen_stub(skip)]
134    fn need_skip(&self) {}
135
136    #[deprecated(since = "1.0.0", note = "This method is deprecated")]
137    fn deprecated_method(&self) {
138        println!("This method is deprecated");
139    }
140
141    #[deprecated(since = "1.0.0", note = "This method is deprecated")]
142    #[getter]
143    fn deprecated_getter(&self) -> usize {
144        self.x
145    }
146
147    #[deprecated(since = "1.0.0", note = "This setter is deprecated")]
148    #[setter]
149    fn set_y(&mut self, value: usize) {
150        self.y = value;
151    }
152
153    #[deprecated(since = "1.0.0", note = "This staticmethod is deprecated")]
154    #[staticmethod]
155    fn deprecated_staticmethod() -> usize {
156        42
157    }
158}
159
160#[gen_stub_pyfunction]
161#[pyfunction]
162#[pyo3(signature = (x = 2))]
163fn create_a(x: usize) -> A {
164    A { x, y: 10 }
165}
166
167#[gen_stub_pyclass]
168#[pyclass(extends=A)]
169#[derive(Debug)]
170struct B;
171
172/// `C` only impl `FromPyObject`
173#[derive(Debug)]
174struct C {
175    x: usize,
176}
177#[gen_stub_pyfunction]
178#[pyfunction(signature = (c=None))]
179fn print_c(c: Option<C>) {
180    if let Some(c) = c {
181        println!("{}", c.x);
182    } else {
183        println!("None");
184    }
185}
186impl FromPyObject<'_> for C {
187    fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult<Self> {
188        Ok(C { x: ob.extract()? })
189    }
190}
191impl pyo3_stub_gen::PyStubType for C {
192    fn type_output() -> pyo3_stub_gen::TypeInfo {
193        usize::type_output()
194    }
195}
196
197/// Returns the length of the string.
198#[gen_stub_pyfunction]
199#[pyfunction]
200fn str_len(x: &str) -> PyResult<usize> {
201    Ok(x.len())
202}
203
204#[gen_stub_pyfunction]
205#[pyfunction]
206fn echo_path(path: PathBuf) -> PyResult<PathBuf> {
207    Ok(path)
208}
209
210#[gen_stub_pyfunction]
211#[pyfunction]
212fn ahash_dict() -> HashMap<String, i32, RandomState> {
213    let mut map: HashMap<String, i32, RandomState> = HashMap::with_hasher(RandomState::new());
214    map.insert("apple".to_string(), 3);
215    map.insert("banana".to_string(), 2);
216    map.insert("orange".to_string(), 5);
217    map
218}
219
220#[gen_stub_pyclass_enum]
221#[pyclass(eq, eq_int)]
222#[derive(Debug, Clone, PartialEq, Eq, Hash)]
223pub enum Number {
224    #[pyo3(name = "FLOAT")]
225    Float,
226    #[pyo3(name = "INTEGER")]
227    Integer,
228}
229
230#[gen_stub_pyclass_enum]
231#[pyclass(eq, eq_int)]
232#[pyo3(rename_all = "UPPERCASE")]
233#[derive(Debug, Clone, PartialEq, Eq, Hash)]
234pub enum NumberRenameAll {
235    /// Float variant
236    Float,
237    Integer,
238}
239
240#[gen_stub_pyclass_complex_enum]
241#[pyclass]
242#[pyo3(rename_all = "UPPERCASE")]
243#[derive(Debug, Clone)]
244pub enum NumberComplex {
245    /// Float variant
246    Float(f64),
247    /// Integer variant
248    #[pyo3(constructor = (int=2))]
249    Integer {
250        /// The integer value
251        int: i32,
252    },
253}
254
255/// Example from PyO3 documentation for complex enum
256/// https://pyo3.rs/v0.25.1/class.html#complex-enums
257#[gen_stub_pyclass_complex_enum]
258#[pyclass]
259enum Shape1 {
260    Circle { radius: f64 },
261    Rectangle { width: f64, height: f64 },
262    RegularPolygon(u32, f64),
263    Nothing {},
264}
265
266/// Example from PyO3 documentation for complex enum
267/// https://pyo3.rs/v0.25.1/class.html#complex-enums
268#[gen_stub_pyclass_complex_enum]
269#[pyclass]
270enum Shape2 {
271    #[pyo3(constructor = (radius=1.0))]
272    Circle {
273        radius: f64,
274    },
275    #[pyo3(constructor = (*, width, height))]
276    Rectangle {
277        width: f64,
278        height: f64,
279    },
280    #[pyo3(constructor = (side_count, radius=1.0))]
281    RegularPolygon {
282        side_count: u32,
283        radius: f64,
284    },
285    Nothing {},
286}
287
288#[gen_stub_pymethods]
289#[pymethods]
290impl Number {
291    #[getter]
292    /// Whether the number is a float.
293    fn is_float(&self) -> bool {
294        matches!(self, Self::Float)
295    }
296
297    #[getter]
298    /// Whether the number is an integer.
299    fn is_integer(&self) -> bool {
300        matches!(self, Self::Integer)
301    }
302}
303
304#[gen_stub_pyclass]
305#[pyclass]
306pub struct DecimalHolder {
307    #[pyo3(get)]
308    value: Decimal,
309}
310
311#[gen_stub_pymethods]
312#[pymethods]
313impl DecimalHolder {
314    #[new]
315    fn new(value: Decimal) -> Self {
316        Self { value }
317    }
318}
319
320module_variable!("pure", "MY_CONSTANT1", usize);
321module_variable!("pure", "MY_CONSTANT2", usize, 123);
322
323#[gen_stub_pyfunction]
324#[pyfunction]
325async fn async_num() -> i32 {
326    123
327}
328
329#[gen_stub_pyfunction]
330#[pyfunction]
331#[deprecated(since = "1.0.0", note = "This function is deprecated")]
332fn deprecated_function() {
333    println!("This function is deprecated");
334}
335
336// Test if non-any PyObject Target can be a default value
337#[gen_stub_pyfunction]
338#[pyfunction]
339#[pyo3(signature = (num = Number::Float))]
340fn default_value(num: Number) -> Number {
341    num
342}
343
344// These are the tests to test the treatment of `*args` and `**kwargs` in functions
345
346/// Test struct for eq and ord comparison methods
347#[gen_stub_pyclass]
348#[pyclass(eq, ord)]
349#[derive(Debug, Clone, PartialEq, PartialOrd)]
350pub struct ComparableStruct {
351    #[pyo3(get)]
352    pub value: i32,
353}
354
355#[gen_stub_pymethods]
356#[pymethods]
357impl ComparableStruct {
358    #[new]
359    fn new(value: i32) -> Self {
360        Self { value }
361    }
362}
363
364/// Test struct for hash and str methods
365#[gen_stub_pyclass]
366#[pyclass(eq, hash, frozen, str)]
367#[derive(Debug, Clone, Hash, PartialEq)]
368pub struct HashableStruct {
369    #[pyo3(get)]
370    pub name: String,
371}
372
373impl std::fmt::Display for HashableStruct {
374    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
375        write!(f, "HashableStruct({})", self.name)
376    }
377}
378
379#[gen_stub_pymethods]
380#[pymethods]
381impl HashableStruct {
382    #[new]
383    fn new(name: String) -> Self {
384        Self { name }
385    }
386}
387
388/// Takes a variable number of arguments and returns their string representation.
389#[gen_stub_pyfunction]
390#[pyfunction]
391#[pyo3(signature = (*args))]
392fn func_with_star_arg_typed(
393    #[gen_stub(override_type(type_repr = "str"))] args: &Bound<PyTuple>,
394) -> String {
395    args.to_string()
396}
397
398/// Takes a variable number of arguments and returns their string representation.
399#[gen_stub_pyfunction]
400#[pyfunction]
401#[pyo3(signature = (*args))]
402fn func_with_star_arg(args: &Bound<PyTuple>) -> String {
403    args.to_string()
404}
405
406/// Takes a variable number of keyword arguments and does nothing
407#[gen_stub_pyfunction]
408#[pyfunction]
409#[pyo3(signature = (**kwargs))]
410fn func_with_kwargs(kwargs: Option<&Bound<PyDict>>) -> bool {
411    kwargs.is_some()
412}
413
414module_doc!("pure", "Document for {} ...", env!("CARGO_PKG_NAME"));
415
416/// Initializes the Python module
417#[pymodule]
418fn pure(m: &Bound<PyModule>) -> PyResult<()> {
419    m.add("MY_CONSTANT1", 19937)?;
420    m.add("MY_CONSTANT2", 123)?;
421    m.add_class::<A>()?;
422    m.add_class::<B>()?;
423    m.add_class::<MyDate>()?;
424    m.add_class::<Number>()?;
425    m.add_class::<NumberRenameAll>()?;
426    m.add_class::<NumberComplex>()?;
427    m.add_class::<Shape1>()?;
428    m.add_class::<Shape2>()?;
429    m.add_class::<ManualSubmit>()?;
430    m.add_class::<PartialManualSubmit>()?;
431    m.add_class::<OverrideType>()?;
432    m.add_class::<ComparableStruct>()?;
433    m.add_class::<HashableStruct>()?;
434    m.add_class::<DecimalHolder>()?;
435    m.add_class::<DataContainer>()?;
436    m.add_class::<Placeholder>()?;
437    m.add_class::<Calculator>()?;
438    m.add_class::<InstanceValue>()?;
439    m.add_class::<Problem>()?;
440    m.add_class::<CustomStubType>()?;
441    m.add_class::<NormalClass>()?;
442    m.add_class::<CustomEnum>()?;
443    m.add_class::<CustomComplexEnum>()?;
444    m.add_function(wrap_pyfunction!(sum, m)?)?;
445    m.add_function(wrap_pyfunction!(create_dict, m)?)?;
446    m.add_function(wrap_pyfunction!(read_dict, m)?)?;
447    m.add_function(wrap_pyfunction!(create_a, m)?)?;
448    m.add_function(wrap_pyfunction!(print_c, m)?)?;
449    m.add_function(wrap_pyfunction!(str_len, m)?)?;
450    m.add_function(wrap_pyfunction!(echo_path, m)?)?;
451    m.add_function(wrap_pyfunction!(ahash_dict, m)?)?;
452    m.add_function(wrap_pyfunction!(async_num, m)?)?;
453    m.add_function(wrap_pyfunction!(deprecated_function, m)?)?;
454    m.add_function(wrap_pyfunction!(default_value, m)?)?;
455    m.add_function(wrap_pyfunction!(fn_override_type, m)?)?;
456    m.add_function(wrap_pyfunction!(fn_with_python_param, m)?)?;
457    m.add_function(wrap_pyfunction!(fn_with_python_stub, m)?)?;
458    m.add_function(wrap_pyfunction!(overload_example_1, m)?)?;
459    m.add_function(wrap_pyfunction!(overload_example_2, m)?)?;
460    m.add_function(wrap_pyfunction!(as_tuple, m)?)?;
461    m.add_function(wrap_pyfunction!(manual_overload_example_1, m)?)?;
462    m.add_function(wrap_pyfunction!(manual_overload_example_2, m)?)?;
463    m.add_function(wrap_pyfunction!(manual_overload_as_tuple, m)?)?;
464    m.add_function(wrap_pyfunction!(add_decimals, m)?)?;
465    m.add_function(wrap_pyfunction!(process_container, m)?)?;
466    m.add_function(wrap_pyfunction!(sum_list, m)?)?;
467    m.add_function(wrap_pyfunction!(create_containers, m)?)?;
468    // Test-cases for `*args` and `**kwargs`
469    m.add_function(wrap_pyfunction!(func_with_star_arg, m)?)?;
470    m.add_function(wrap_pyfunction!(func_with_star_arg_typed, m)?)?;
471    m.add_function(wrap_pyfunction!(func_with_kwargs, m)?)?;
472
473    // Test cases for type: ignore functionality
474    m.add_function(wrap_pyfunction!(test_type_ignore_specific, m)?)?;
475    m.add_function(wrap_pyfunction!(test_type_ignore_all, m)?)?;
476    m.add_function(wrap_pyfunction!(test_type_ignore_pyright, m)?)?;
477    m.add_function(wrap_pyfunction!(test_type_ignore_custom, m)?)?;
478    m.add_function(wrap_pyfunction!(test_type_ignore_no_comment_all, m)?)?;
479    m.add_function(wrap_pyfunction!(test_type_ignore_no_comment_specific, m)?)?;
480
481    // Test case for custom exceptions
482    m.add("MyError", m.py().get_type::<MyError>())?;
483    m.add_class::<NotIntError>()?;
484
485    // Test class for type: ignore functionality
486    m.add_class::<TypeIgnoreTest>()?;
487    Ok(())
488}
489
490/// Test function with type: ignore for specific rules
491#[gen_stub_pyfunction]
492#[gen_stub(type_ignore = ["arg-type", "return-value"])]
493#[pyfunction]
494fn test_type_ignore_specific() -> i32 {
495    42
496}
497
498/// Test function with type: ignore (without equals for catch-all)
499#[gen_stub_pyfunction]
500#[gen_stub(type_ignore)]
501#[pyfunction]
502fn test_type_ignore_all() -> i32 {
503    42
504}
505
506/// Test function with Pyright diagnostic rules
507#[gen_stub_pyfunction]
508#[gen_stub(type_ignore = ["reportGeneralTypeIssues", "reportReturnType"])]
509#[pyfunction]
510fn test_type_ignore_pyright() -> i32 {
511    42
512}
513
514/// Test function with custom (unknown) rule
515#[gen_stub_pyfunction]
516#[gen_stub(type_ignore = ["custom-rule", "attr-defined"])]
517#[pyfunction]
518fn test_type_ignore_custom() -> i32 {
519    42
520}
521
522// NOTE: Doc-comment MUST NOT be added to the next function,
523// as it tests if `type_ignore` without no doccomment is handled correctly;
524// i.e. it emits comment after `...`, not before.
525
526#[gen_stub_pyfunction]
527#[gen_stub(type_ignore)]
528#[pyfunction]
529fn test_type_ignore_no_comment_all() -> i32 {
530    42
531}
532
533#[gen_stub_pyfunction]
534#[gen_stub(type_ignore=["arg-type", "reportIncompatibleMethodOverride"])]
535#[pyfunction]
536fn test_type_ignore_no_comment_specific() -> i32 {
537    42
538}
539
540/// Test class for method type: ignore functionality
541#[gen_stub_pyclass]
542#[pyclass]
543pub struct TypeIgnoreTest {}
544
545#[gen_stub_pymethods]
546#[pymethods]
547impl TypeIgnoreTest {
548    #[new]
549    fn new() -> Self {
550        Self {}
551    }
552
553    /// Test method with type: ignore for specific rules
554    #[gen_stub(type_ignore = ["union-attr", "return-value"])]
555    fn test_method_ignore(&self, value: i32) -> i32 {
556        value * 2
557    }
558
559    /// Test method with type: ignore (without equals for catch-all)
560    #[gen_stub(type_ignore)]
561    fn test_method_all_ignore(&self) -> i32 {
562        42
563    }
564}
565
566define_stub_info_gatherer!(stub_info);
567
568/// Test of unit test for testing link problem
569#[cfg(test)]
570mod test {
571    #[test]
572    fn test() {
573        assert_eq!(2 + 2, 4);
574    }
575}