py_rs/
lib.rs

1//! <h1 align="center" style="padding-top: 0; margin-top: 0;">
2//! <img width="150px" src="https://raw.githubusercontent.com/Blue-Chestnut/py-rs/main/logo.png" alt="logo">
3//! <br/>
4//! py-rs
5//! </h1>
6//! <p align="center">
7//! Generate python type declarations from rust types
8//! </p>
9//!
10//! <div align="center">
11//! <!-- Github Actions -->
12//! <img src="https://img.shields.io/github/actions/workflow/status/Blue-Chestnut/py-rs/test.yml?branch=main" alt="actions status" />
13//! <a href="https://crates.io/crates/py-rs">
14//! <img src="https://img.shields.io/crates/v/py-rs.svg?style=flat-square"
15//! alt="Crates.io version" />
16//! </a>
17//! <a href="https://docs.rs/py-rs">
18//! <img src="https://img.shields.io/badge/docs-latest-blue.svg?style=flat-square"
19//! alt="docs.rs docs" />
20//! </a>
21//! <a href="https://crates.io/crates/py-rs">
22//! <img src="https://img.shields.io/crates/d/py-rs.svg?style=flat-square"
23//! alt="Download" />
24//! </a>
25//! </div>
26//!
27//! ## Why?
28//! When building an api in rust, data structures have to be shared between backend
29//! and client. Using this library, you can easily generate python bindings to your
30//! rust structs & enums so that you can keep your types in one place.
31//!
32//! > **Note:** This is a work in progress. There are still some features of `ts-rs` that are not tested or converted.
33//!
34//! ## How?
35//! py-rs exposes a single trait, `PY`. Using a derive macro, you can implement this interface for your types.
36//! Then, you can use this trait to obtain the Python bindings.
37//! We recommend doing this in your tests.
38//! [See the example](https://github.com/Blue-Chestnut/py-rs/blob/main/example/src/lib.rs) and [the docs](https://docs.rs/py-rs/latest/py_rs/).
39//!
40//! ## Get started
41//! ```toml
42//! [dependencies]
43//! py-rs = "0.1.0"
44//! ```
45//!
46//! ```rust
47//! use py_rs::PY;
48//!
49//! #[derive(PY)]
50//! #[py(export)]
51//! struct User {
52//!     user_id: i32,
53//!     first_name: String,
54//!     last_name: String,
55//! }
56//! ```
57//!
58//! When running `cargo test` or `cargo test export_bindings`, the Python bindings will be exported to the file `bindings/User.py`
59//! and will contain the following code:
60//!
61//! ```python
62//! from pydanic import BaseModel
63//!
64//! class User(BaseModel):
65//!     user_id: int
66//!     first_name: str
67//!     last_name: str
68//! ```
69//!
70//! ## Features
71//! - generate type declarations from rust structs
72//! - generate union declarations from rust enums
73//! - generate necessary imports when exporting to multiple files
74//! - serde compatibility
75//! - generic types
76//! - support for ESM imports
77//!
78//! > **Note:** not all the features are tested for Python.
79//!
80//! ## cargo features
81//! | **Feature**        | **Description**                                                                                                                                                                                           |
82//! |:-------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
83//! | serde-compat       | **Enabled by default** <br/>See the *"serde compatibility"* section below for more information.                                                                                                           |
84//! | format             | Enables formatting of the generated Python bindings. <br/>Currently, this unfortunately adds quite a few dependencies.                                                                                    |
85//! | no-serde-warnings  | By default, warnings are printed during build if unsupported serde attributes are encountered. <br/>Enabling this feature silences these warnings.                                                        |
86//! | serde-json-impl    | Implement `PY` for types from *serde_json*                                                                                                                                                                |
87//! | chrono-impl        | Implement `PY` for types from *chrono*                                                                                                                                                                    |
88//! | bigdecimal-impl    | Implement `PY` for types from *bigdecimal*                                                                                                                                                                |
89//! | url-impl           | Implement `PY` for types from *url*                                                                                                                                                                       |
90//! | uuid-impl          | Implement `PY` for types from *uuid*                                                                                                                                                                      |
91//! | bson-uuid-impl     | Implement `PY` for *bson::oid::ObjectId* and *bson::uuid*                                                                                                                                                 |
92//! | bytes-impl         | Implement `PY` for types from *bytes*                                                                                                                                                                     |
93//! | indexmap-impl      | Implement `PY` for types from *indexmap*                                                                                                                                                                  |
94//! | ordered-float-impl | Implement `PY` for types from *ordered_float*                                                                                                                                                             |
95//! | heapless-impl      | Implement `PY` for types from *heapless*                                                                                                                                                                  |
96//! | semver-impl        | Implement `PY` for types from *semver*                                                                                                                                                                    |
97//! | smol_str-impl      | Implement `PY` for types from *smol_str*                                                                                                                                                                  |
98//! | tokio-impl         | Implement `PY` for types from *tokio*                                                                                                                                                                     |
99//!
100//! <br/>
101//!
102//! If there's a type you're dealing with which doesn't implement `PY`, use either
103//! `#[py(as = "..")]` or `#[py(type = "..")]`, or open a PR.
104//!
105//! ## `serde` compatability
106//! With the `serde-compat` feature (enabled by default), serde attributes can be parsed for enums and structs.
107//! Supported serde attributes:
108//! - `rename`
109//! - `rename-all`
110//! - `rename-all-fields`
111//! - `tag`
112//! - `content`
113//! - `untagged`
114//! - `skip`
115//! - `flatten`
116//! - `default`
117//!
118//! Note: `skip_serializing` and `skip_deserializing` are ignored. If you wish to exclude a field
119//! from the generated type, but cannot use `#[serde(skip)]`, use `#[py(skip)]` instead.
120//!
121//! When py-rs encounters an unsupported serde attribute, a warning is emitted, unless the feature `no-serde-warnings` is enabled.
122//!
123//! ## Contributing
124//! Contributions are always welcome!
125//! Feel free to open an issue, discuss using GitHub discussions or open a PR.
126//! [See CONTRIBUTING.md](https://github.com/Blue-Chestnut/py-rs/blob/main/CONTRIBUTING.md)
127//!
128//! ## MSRV
129//! The Minimum Supported Rust Version for this crate is 1.63.0
130
131use std::{
132    any::TypeId,
133    collections::{BTreeMap, BTreeSet, HashMap, HashSet},
134    net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
135    num::{
136        NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize, NonZeroU128,
137        NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize,
138    },
139    ops::{Range, RangeInclusive},
140    path::{Path, PathBuf},
141};
142
143pub use py_rs_macros::PY;
144
145pub use crate::export::ExportError;
146
147#[cfg(feature = "chrono-impl")]
148mod chrono;
149mod export;
150#[cfg(feature = "serde-json-impl")]
151mod serde_json;
152#[cfg(feature = "tokio-impl")]
153mod tokio;
154
155/// A type which can be represented in Python.  
156/// Most of the time, you'd want to derive this trait instead of implementing it manually.  
157/// py-rs comes with implementations for all primitives, most collections, tuples,
158/// arrays and containers.
159///
160/// ### exporting
161/// Because Rusts procedural macros are evaluated before other compilation steps, Python
162/// bindings __cannot__ be exported during compile time.
163///
164/// Bindings can be exported within a test, which py-rs generates for you by adding `#[py(export)]`
165/// to a type you wish to export to a file.  
166/// When `cargo test` is run, all types annotated with `#[py(export)]` and all of their
167/// dependencies will be written to `PY_RS_EXPORT_DIR`, or `./bindings` by default.
168///
169/// For each individual type, path and filename within the output directory can be changed using
170/// `#[py(export_to = "...")]`. By default, the filename will be derived from the name of the type.
171///
172/// If, for some reason, you need to do this during runtime or cannot use `#[py(export)]`, bindings
173/// can be exported manually:
174///
175/// | Function              | Includes Dependencies | To                 |
176/// |-----------------------|-----------------------|--------------------|
177/// | [`PY::export`]        | ❌                    | `PY_RS_EXPORT_DIR` |
178/// | [`PY::export_all`]    | ✔️                    | `PY_RS_EXPORT_DIR` |
179/// | [`PY::export_all_to`] | ✔️                    | _custom_           |
180///
181/// ### serde compatibility
182/// By default, the feature `serde-compat` is enabled.
183/// py-rs then parses serde attributes and adjusts the generated python bindings accordingly.
184/// Not all serde attributes are supported yet - if you use an unsupported attribute, you'll see a
185/// warning.
186///
187/// ### container attributes
188/// attributes applicable for both structs and enums
189///
190/// - **`#[py(crate = "..")]`**
191///   Generates code which references the module passed to it instead of defaulting to `::py_rs`
192///   This is useful for cases where you have to re-export the crate.
193///
194/// - **`#[py(export)]`**  
195///   Generates a test which will export the type, by default to `bindings/<name>.py` when running
196///   `cargo test`. The default base directory can be overridden with the `PY_RS_EXPORT_DIR` environment variable.
197///   Adding the variable to a project's [config.toml](https://doc.rust-lang.org/cargo/reference/config.html#env) can
198///   make it easier to manage.
199///   ```toml
200///   # <project-root>/.cargo/config.toml
201///   [env]
202///   PY_RS_EXPORT_DIR = { value = "<OVERRIDE_DIR>", relative = true }
203///   ```
204///   <br/>
205///
206/// - **`#[py(export_to = "..")]`**  
207///   Specifies where the type should be exported to. Defaults to `<name>.py`.  
208///   The path given to the `export_to` attribute is relative to the `PY_RS_EXPORT_DIR` environment variable,
209///   or, if `PY_RS_EXPORT_DIR` is not set, to `./bindings`  
210///   If the provided path ends in a trailing `/`, it is interpreted as a directory.   
211///   Note that you need to add the `export` attribute as well, in order to generate a test which exports the type.
212///   <br/><br/>
213///
214/// - **`#[py(as = "..")]`**  
215///   Overrides the type used in Python, using the provided Rust type instead.
216///   This is useful when you have a custom serializer and deserializer and don't want to implement `PY` manually
217///   <br/><br/>
218///
219/// - **`#[py(type = "..")]`**  
220///   Overrides the type used in Python.  
221///   This is useful when you have a custom serializer and deserializer and don't want to implement `PY` manually
222///   <br/><br/>
223///
224/// - **`#[py(rename = "..")]`**  
225///   Sets the python name of the generated type
226///   <br/><br/>
227///
228/// - **`#[py(rename_all = "..")]`**  
229///   Rename all fields/variants of the type.  
230///   Valid values are `lowercase`, `UPPERCASE`, `camelCase`, `snake_case`, `PascalCase`, `SCREAMING_SNAKE_CASE`, "kebab-case" and "SCREAMING-KEBAB-CASE"
231///   <br/><br/>
232///
233/// - **`#[py(concrete(..)]`**  
234///   Disables one ore more generic type parameters by specifying a concrete type for them.  
235///   The resulting Python definition will not be generic over these parameters and will use the
236///   provided type instead.  
237///   This is especially useful for generic types containing associated types. Since Python does
238///   not have an equivalent construct to associated types, we cannot generate a generic definition
239///   for them. Using `#[py(concrete(..)]`, we can however generate a non-generic definition.
240///   Example:
241///   ```
242///   # use py_rs::PY;
243///   ##[derive(PY)]
244///   ##[py(concrete(I = std::vec::IntoIter<String>))]
245///   struct SearchResult<I: Iterator>(Vec<I::Item>);
246///   // will always generate `type SearchResult = list[str]`.
247///   ```
248///   <br/><br/>
249///
250/// - **`#[py(bound)]`**
251///   Override the bounds generated on the `PY` implementation for this type. This is useful in
252///   combination with `#[py(concrete)]`, when the type's generic parameters aren't directly used
253///   in a field or variant.
254///
255///   Example:
256///   ```
257///   # use py_rs::PY;
258///
259///   trait Container {
260///       type Value: PY;
261///   }
262///
263///   struct MyContainer;
264///
265///   ##[derive(PY)]
266///   struct MyValue;
267///
268///   impl Container for MyContainer {
269///       type Value = MyValue;
270///   }
271///
272///   ##[derive(PY)]
273///   ##[py(export, concrete(C = MyContainer))]
274///   struct Inner<C: Container> {
275///       value: C::Value,
276///   }
277///
278///   ##[derive(PY)]
279///   // Without `#[py(bound)]`, `#[derive(PY)]` would generate an unnecessary
280///   // `C: PY` bound
281///   ##[py(export, concrete(C = MyContainer), bound = "C::Value: PY")]
282///   struct Outer<C: Container> {
283///       inner: Inner<C>,
284///   }
285///   ```
286///   <br/><br/>
287///
288/// ### struct attributes
289/// - **`#[py(tag = "..")]`**  
290///   Include the structs name (or value of `#[py(rename = "..")]`) as a field with the given key.
291///   <br/><br/>
292///
293/// ### struct field attributes
294///
295/// - **`#[py(type = "..")]`**  
296///   Overrides the type used in Python.  
297///   This is useful when there's a type for which you cannot derive `PY`.
298///   <br/><br/>
299///
300/// - **`#[py(as = "..")]`**  
301///   Overrides the type of the annotated field, using the provided Rust type instead.
302///   This is useful when there's a type for which you cannot derive `PY`.  
303///   `_` may be used to refer to the type of the field, e.g `#[py(as = "Option<_>")]`.
304///   <br/><br/>
305///
306/// - **`#[py(rename = "..")]`**  
307///   Renames this field. To rename all fields of a struct, see the container attribute `#[py(rename_all = "..")]`.
308///   <br/><br/>
309///
310/// - **`#[py(inline)]`**  
311///   Inlines the type of this field, replacing its name with its definition.
312///   <br/><br/>
313///
314/// - **`#[py(skip)]`**  
315///   Skips this field, omitting it from the generated *Python* type.
316///   <br/><br/>
317///
318/// - **`#[py(optional)]`**  
319///   May be applied on a struct field of type `Option<T>`. By default, such a field would turn into `t: T | None`.  
320///   If `#[py(optional)]` is present, `t?: T` is generated instead.  
321///   If `#[py(optional = nullable)]` is present, `t?: T | None` is generated.
322///   <br/><br/>
323///
324/// - **`#[py(flatten)]`**  
325///   Flatten this field, inlining all the keys of the field's type into its parent.
326///   <br/><br/>
327///   
328/// ### enum attributes
329///
330/// - **`#[py(tag = "..")]`**  
331///   Changes the representation of the enum to store its tag in a separate field.  
332///   See [the serde docs](https://serde.rs/enum-representations.html) for more information.
333///   <br/><br/>
334///
335/// - **`#[py(content = "..")]`**  
336///   Changes the representation of the enum to store its content in a separate field.  
337///   See [the serde docs](https://serde.rs/enum-representations.html) for more information.
338///   <br/><br/>
339///
340/// - **`#[py(untagged)]`**  
341///   Changes the representation of the enum to not include its tag.  
342///   See [the serde docs](https://serde.rs/enum-representations.html) for more information.
343///   <br/><br/>
344///
345/// - **`#[py(rename_all = "..")]`**  
346///   Rename all variants of this enum.  
347///   Valid values are `lowercase`, `UPPERCASE`, `camelCase`, `snake_case`, `PascalCase`, `SCREAMING_SNAKE_CASE`, "kebab-case" and "SCREAMING-KEBAB-CASE"
348///   <br/><br/>
349///
350/// - **`#[py(rename_all_fields = "..")]`**  
351///   Renames the fields of all the struct variants of this enum. This is equivalent to using
352///   `#[py(rename_all = "..")]` on all of the enum's variants.
353///   Valid values are `lowercase`, `UPPERCASE`, `camelCase`, `snake_case`, `PascalCase`, `SCREAMING_SNAKE_CASE`, "kebab-case" and "SCREAMING-KEBAB-CASE"
354///   <br/><br/>
355///  
356/// ### enum variant attributes
357///
358/// - **`#[py(rename = "..")]`**  
359///   Renames this variant. To rename all variants of an enum, see the container attribute `#[py(rename_all = "..")]`.
360///   <br/><br/>
361///
362/// - **`#[py(skip)]`**  
363///   Skip this variant, omitting it from the generated *Python* type.
364///   <br/><br/>
365///
366/// - **`#[py(untagged)]`**  
367///   Changes this variant to be treated as if the enum was untagged, regardless of the enum's tag
368///   and content attributes
369///   <br/><br/>
370///
371/// - **`#[py(rename_all = "..")]`**  
372///   Renames all the fields of a struct variant.
373///   Valid values are `lowercase`, `UPPERCASE`, `camelCase`, `snake_case`, `PascalCase`, `SCREAMING_SNAKE_CASE`, "kebab-case" and "SCREAMING-KEBAB-CASE"
374///   <br/><br/>
375pub trait PY {
376    /// If this type does not have generic parameters, then `WithoutGenerics` should just be `Self`.
377    /// If the type does have generic parameters, then all generic parameters must be replaced with
378    /// a dummy type, e.g `py_rs::Dummy` or `()`.
379    /// The only requirement for these dummy types is that `EXPORT_TO` must be `None`.
380    ///
381    /// # Example:
382    /// ```
383    /// use py_rs::PY;
384    /// struct GenericType<A, B>(A, B);
385    /// impl<A, B> PY for GenericType<A, B> {
386    ///     type WithoutGenerics = GenericType<py_rs::Dummy, py_rs::Dummy>;
387    ///     // ...
388    ///     # fn decl() -> String { todo!() }
389    ///     # fn decl_concrete() -> String { todo!() }
390    ///     # fn name() -> String { todo!() }
391    ///     # fn inline() -> String { todo!() }
392    ///     # fn inline_flattened() -> String { todo!() }
393    /// }
394    /// ```
395    type WithoutGenerics: PY + ?Sized;
396
397    /// comment to describe this type in Python - when `PY` is derived, docs are
398    /// automatically read from your doc comments or `#[doc = ".."]` attributes
399    const DOCS: Option<&'static str> = None;
400
401    /// Identifier of this type, excluding generic parameters.
402    fn ident() -> String {
403        // by default, fall back to `PY::name()`.
404        let name = Self::name();
405
406        match name.find('<') {
407            // TODO check if this needs to be changed. I guess this is because of things like Array<Number>
408            Some(i) => name[..i].to_owned(),
409            None => name,
410        }
411    }
412
413    /// Generate the classes for variants of enums
414    /// Python does not support nested enum types like Rust
415    /// nor are Union types well supported like in TypeScript
416    /// For convinience we generate a enum with all the types
417    /// of the enum as a class without the nesting.
418    /// # Example:
419    /// ```
420    /// #[derive(Serialize, PY)]
421    /// #[serde(tag = "kind", content = "data")]
422    /// #[py(export)]
423    /// enum ComplexEnum {
424    ///     A,
425    ///     B { foo: String, bar: f64 },
426    ///     W(SimpleEnum),
427    ///     F { nested: SimpleEnum },
428    ///     V(Vec<Series>),
429    ///     U(Box<User>),
430    /// }
431    /// ```
432    /// ```python
433    /// class ComplexEnumIdentifier(StrEnum):
434    ///     A = "A"
435    ///     B = "B"
436    ///     W = "W"
437    ///     F = "F"
438    ///     V = "V"
439    ///     U = "U"
440    ///
441    /// ...
442    ///
443    ///  ```
444    fn variant_classes_decl() -> String;
445
446    /// Declaration of this type, e.g. `type User = { user_id: number, ... }`. TODO update
447    /// This function will panic if the type has no declaration.
448    ///
449    /// If this type is generic, then all provided generic parameters will be swapped for
450    /// placeholders, resulting in a generic python definition.
451    /// Both `SomeType::<i32>::decl()` and `SomeType::<String>::decl()` will therefore result in
452    /// the same Python declaration `T = TypeVar('T')\n type SomeType[T] = ...`.
453    fn decl() -> String;
454
455    /// Declaration of this type using the supplied generic arguments.
456    /// The resulting Python definition will not be generic. For that, see `PY::decl()`.
457    /// If this type is not generic, then this function is equivalent to `PY::decl()`.
458    fn decl_concrete() -> String;
459
460    /// Name of this type in Python, including generic parameters
461    fn name() -> String;
462
463    /// Formats this types definition in Python, e.g `{ user_id: number }`.
464    /// This function will panic if the type cannot be inlined.
465    fn inline() -> String;
466
467    /// Flatten a type declaration.  
468    /// This function will panic if the type cannot be flattened.
469    fn inline_flattened() -> String;
470
471    /// Iterates over all dependency of this type.
472    fn visit_dependencies(_: &mut impl TypeVisitor)
473    where
474        Self: 'static,
475    {
476    }
477
478    /// Iterates over all type parameters of this type.
479    fn visit_generics(_: &mut impl TypeVisitor)
480    where
481        Self: 'static,
482    {
483    }
484
485    /// Resolves all dependencies of this type recursively.
486    fn dependencies() -> Vec<Dependency>
487    where
488        Self: 'static,
489    {
490        let mut deps: Vec<Dependency> = vec![];
491        struct Visit<'a>(&'a mut Vec<Dependency>);
492        impl<'a> TypeVisitor for Visit<'a> {
493            fn visit<T: PY + 'static + ?Sized>(&mut self) {
494                if let Some(dep) = Dependency::from_ty::<T>() {
495                    self.0.push(dep);
496                }
497            }
498        }
499        Self::visit_dependencies(&mut Visit(&mut deps));
500
501        deps
502    }
503
504    /// Manually export this type to the filesystem.
505    /// To export this type together with all of its dependencies, use [`PY::export_all`].
506    ///
507    /// # Automatic Exporting
508    /// Types annotated with `#[py(export)]`, together with all of their dependencies, will be
509    /// exported automatically whenever `cargo test` is run.  
510    /// In that case, there is no need to manually call this function.
511    ///
512    /// # Target Directory
513    /// The target directory to which the type will be exported may be changed by setting the
514    /// `PY_RS_EXPORT_DIR` environment variable. By default, `./bindings` will be used.
515    ///
516    /// To specify a target directory manually, use [`PY::export_all_to`], which also exports all
517    /// dependencies.
518    ///
519    /// To alter the filename or path of the type within the target directory,
520    /// use `#[py(export_to = "...")]`.
521    fn export() -> Result<(), ExportError>
522    where
523        Self: 'static,
524    {
525        let path = Self::default_output_path()
526            .ok_or_else(std::any::type_name::<Self>)
527            .map_err(ExportError::CannotBeExported)?;
528
529        export::export_to::<Self, _>(path)
530    }
531
532    /// Manually export this type to the filesystem, together with all of its dependencies.  
533    /// To export only this type, without its dependencies, use [`PY::export`].
534    ///
535    /// # Automatic Exporting
536    /// Types annotated with `#[py(export)]`, together with all of their dependencies, will be
537    /// exported automatically whenever `cargo test` is run.  
538    /// In that case, there is no need to manually call this function.
539    ///
540    /// # Target Directory
541    /// The target directory to which the types will be exported may be changed by setting the
542    /// `PY_RS_EXPORT_DIR` environment variable. By default, `./bindings` will be used.
543    ///
544    /// To specify a target directory manually, use [`PY::export_all_to`].
545    ///
546    /// To alter the filenames or paths of the types within the target directory,
547    /// use `#[py(export_to = "...")]`.
548    fn export_all() -> Result<(), ExportError>
549    where
550        Self: 'static,
551    {
552        export::export_all_into::<Self>(&*export::default_out_dir())
553    }
554
555    /// Manually export this type into the given directory, together with all of its dependencies.  
556    /// To export only this type, without its dependencies, use [`PY::export`].
557    ///
558    /// Unlike [`PY::export_all`], this function disregards `PY_RS_EXPORT_DIR`, using the provided
559    /// directory instead.
560    ///
561    /// To alter the filenames or paths of the types within the target directory,
562    /// use `#[py(export_to = "...")]`.
563    ///
564    /// # Automatic Exporting
565    /// Types annotated with `#[py(export)]`, together with all of their dependencies, will be
566    /// exported automatically whenever `cargo test` is run.  
567    /// In that case, there is no need to manually call this function.
568    fn export_all_to(out_dir: impl AsRef<Path>) -> Result<(), ExportError>
569    where
570        Self: 'static,
571    {
572        export::export_all_into::<Self>(out_dir)
573    }
574
575    /// Manually generate bindings for this type, returning a [`String`].  
576    /// This function does not format the output, even if the `format` feature is enabled.
577    ///
578    /// # Automatic Exporting
579    /// Types annotated with `#[py(export)]`, together with all of their dependencies, will be
580    /// exported automatically whenever `cargo test` is run.  
581    /// In that case, there is no need to manually call this function.
582    fn export_to_string() -> Result<String, ExportError>
583    where
584        Self: 'static,
585    {
586        export::export_to_string::<Self>()
587    }
588
589    /// Returns the output path to where `T` should be exported.  
590    /// The returned path does _not_ include the base directory from `PY_RS_EXPORT_DIR`.  
591    ///
592    /// To get the output path containing `PY_RS_EXPORT_DIR`, use [`PY::default_output_path`].
593    ///
594    /// When deriving `PY`, the output path can be altered using `#[py(export_to = "...")]`.  
595    /// See the documentation of [`PY`] for more details.
596    ///
597    /// The output of this function depends on the environment variable `PY_RS_EXPORT_DIR`, which is
598    /// used as base directory. If it is not set, `./bindings` is used as default directory.
599    ///
600    /// If `T` cannot be exported (e.g because it's a primitive type), this function will return
601    /// `None`.
602    fn output_path() -> Option<&'static Path> {
603        None
604    }
605
606    /// Returns the output path to where `T` should be exported.  
607    ///
608    /// The output of this function depends on the environment variable `PY_RS_EXPORT_DIR`, which is
609    /// used as base directory. If it is not set, `./bindings` is used as default directory.
610    ///
611    /// To get the output path relative to `PY_RS_EXPORT_DIR` and without reading the environment
612    /// variable, use [`PY::output_path`].
613    ///
614    /// When deriving `PY`, the output path can be altered using `#[py(export_to = "...")]`.  
615    /// See the documentation of [`PY`] for more details.
616    ///
617    /// If `T` cannot be exported (e.g because it's a primitive type), this function will return
618    /// `None`.
619    fn default_output_path() -> Option<PathBuf> {
620        Some(export::default_out_dir().join(Self::output_path()?))
621    }
622}
623
624/// A visitor used to iterate over all dependencies or generics of a type.
625/// When an instance of [`TypeVisitor`] is passed to [`PY::visit_dependencies`] or
626/// [`PY::visit_generics`], the [`TypeVisitor::visit`] method will be invoked for every dependency
627/// or generic parameter respectively.
628pub trait TypeVisitor: Sized {
629    fn visit<T: PY + 'static + ?Sized>(&mut self);
630}
631
632/// A python type which is depended upon by other types.
633/// This information is required for generating the correct import statements.
634#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
635pub struct Dependency {
636    /// Type ID of the rust type
637    pub type_id: TypeId,
638    /// Name of the type in Python
639    pub py_name: String,
640    /// Path to where the type would be exported. By default a filename is derived from the types
641    /// name, which can be customized with `#[py(export_to = "..")]`.  
642    /// This path does _not_ include a base directory.
643    pub output_path: &'static Path,
644}
645
646impl Dependency {
647    /// Constructs a [`Dependency`] from the given type `T`.
648    /// If `T` is not exportable (meaning `T::EXPORT_TO` is `None`), this function will return
649    /// `None`
650    pub fn from_ty<T: PY + 'static + ?Sized>() -> Option<Self> {
651        let output_path = T::output_path()?;
652        Some(Dependency {
653            type_id: TypeId::of::<T>(),
654            py_name: T::ident(),
655            output_path,
656        })
657    }
658}
659
660// generate impls for primitive types
661macro_rules! impl_primitives {
662    ($($($ty:ty),* => $l:literal),*) => { $($(
663        impl PY for $ty {
664            type WithoutGenerics = Self;
665            fn name() -> String { $l.to_owned() }
666            fn inline() -> String { <Self as $crate::PY>::name() }
667            fn inline_flattened() -> String { panic!("{} cannot be flattened", <Self as $crate::PY>::name()) }
668            fn decl() -> String { panic!("{} cannot be declared", <Self as $crate::PY>::name()) }
669            fn decl_concrete() -> String { panic!("{} cannot be declared", <Self as $crate::PY>::name()) }
670            fn variant_classes_decl() -> String { panic!("{} cannot be declared", <Self as $crate::PY>::name()) }
671        }
672    )*)* };
673}
674// generate impls for tuples
675macro_rules! impl_tuples {
676    ( impl $($i:ident),* ) => {
677        impl<$($i: PY),*> PY for ($($i,)*) {
678            type WithoutGenerics = (Dummy, );
679            fn name() -> String {
680                format!("tuple[{}]", [$(<$i as $crate::PY>::name()),*].join(", "))
681            }
682            fn inline() -> String {
683                panic!("tuple cannot be inlined!");
684            }
685            fn visit_generics(v: &mut impl TypeVisitor)
686            where
687                Self: 'static
688            {
689                $(
690                    v.visit::<$i>();
691                    <$i>::visit_generics(v);
692                )*
693            }
694            fn inline_flattened() -> String { panic!("tuple cannot be flattened") }
695            fn decl() -> String { panic!("tuple cannot be declared") }
696            fn decl_concrete() -> String { panic!("tuple cannot be declared") }
697            fn variant_classes_decl() -> String { panic!("{} cannot be declared", <Self as $crate::PY>::name()) }
698        }
699    };
700    ( $i2:ident $(, $i:ident)* ) => {
701        impl_tuples!(impl $i2 $(, $i)* );
702        impl_tuples!($($i),*);
703    };
704    () => {};
705}
706
707// generate impls for wrapper types
708macro_rules! impl_wrapper {
709    ($($t:tt)*) => {
710        $($t)* {
711            type WithoutGenerics = Self;
712            fn name() -> String { T::name() }
713            fn inline() -> String { T::inline() }
714            fn inline_flattened() -> String { T::inline_flattened() }
715            fn visit_dependencies(v: &mut impl TypeVisitor)
716            where
717                Self: 'static,
718            {
719                T::visit_dependencies(v);
720            }
721
722            fn visit_generics(v: &mut impl TypeVisitor)
723            where
724                Self: 'static,
725            {
726                T::visit_generics(v);
727                v.visit::<T>();
728            }
729            fn decl() -> String { panic!("wrapper type cannot be declared") }
730            fn decl_concrete() -> String { panic!("wrapper type cannot be declared") }
731            fn variant_classes_decl() -> String { panic!("{} cannot be declared", <Self as $crate::PY>::name()) }
732        }
733    };
734}
735
736// implement PY for the $shadow, deferring to the impl $s
737macro_rules! impl_shadow {
738    (as $s:ty: $($impl:tt)*) => {
739        $($impl)* {
740            type WithoutGenerics = <$s as $crate::PY>::WithoutGenerics;
741            fn ident() -> String { <$s as $crate::PY>::ident() }
742            fn name() -> String { <$s as $crate::PY>::name() }
743            fn inline() -> String { <$s as $crate::PY>::inline() }
744            fn inline_flattened() -> String { <$s as $crate::PY>::inline_flattened() }
745            fn visit_dependencies(v: &mut impl $crate::TypeVisitor)
746            where
747                Self: 'static,
748            {
749                <$s as $crate::PY>::visit_dependencies(v);
750            }
751            fn visit_generics(v: &mut impl $crate::TypeVisitor)
752            where
753                Self: 'static,
754            {
755                <$s as $crate::PY>::visit_generics(v);
756            }
757            fn decl() -> String { <$s as $crate::PY>::decl() }
758            fn decl_concrete() -> String { <$s as $crate::PY>::decl_concrete() }
759            fn output_path() -> Option<&'static std::path::Path> { <$s as $crate::PY>::output_path() }
760            fn variant_classes_decl() -> String { panic!("{} cannot be declared", <Self as $crate::PY>::name()) }
761        }
762    };
763}
764
765impl<T: PY> PY for Option<T> {
766    type WithoutGenerics = Self;
767
768    fn name() -> String {
769        format!("{} | None", T::name())
770    }
771
772    fn inline() -> String {
773        format!("{} | None", T::inline())
774    }
775
776    fn visit_dependencies(v: &mut impl TypeVisitor)
777    where
778        Self: 'static,
779    {
780        T::visit_dependencies(v);
781    }
782
783    fn visit_generics(v: &mut impl TypeVisitor)
784    where
785        Self: 'static,
786    {
787        T::visit_generics(v);
788        v.visit::<T>();
789    }
790
791    fn decl() -> String {
792        panic!("{} cannot be declared", Self::name())
793    }
794
795    fn decl_concrete() -> String {
796        panic!("{} cannot be declared", Self::name())
797    }
798
799    fn inline_flattened() -> String {
800        panic!("{} cannot be flattened", Self::name())
801    }
802    fn variant_classes_decl() -> String {
803        panic!("{} cannot be declared", Self::name())
804    }
805}
806
807impl<T: PY, E: PY> PY for Result<T, E> {
808    // TODO update
809    // maybe just not support this or add the result import from python
810    type WithoutGenerics = Result<Dummy, Dummy>;
811
812    fn name() -> String {
813        format!("{{ Ok : {} }} | {{ Err : {} }}", T::name(), E::name())
814    }
815
816    fn inline() -> String {
817        format!("{{ Ok : {} }} | {{ Err : {} }}", T::inline(), E::inline())
818    }
819
820    fn visit_dependencies(v: &mut impl TypeVisitor)
821    where
822        Self: 'static,
823    {
824        T::visit_dependencies(v);
825        E::visit_dependencies(v);
826    }
827
828    fn visit_generics(v: &mut impl TypeVisitor)
829    where
830        Self: 'static,
831    {
832        T::visit_generics(v);
833        v.visit::<T>();
834        E::visit_generics(v);
835        v.visit::<E>();
836    }
837
838    fn decl() -> String {
839        panic!("{} cannot be declared", Self::name())
840    }
841
842    fn decl_concrete() -> String {
843        panic!("{} cannot be declared", Self::name())
844    }
845
846    fn inline_flattened() -> String {
847        panic!("{} cannot be flattened", Self::name())
848    }
849    fn variant_classes_decl() -> String {
850        panic!("{} cannot be declared", Self::name())
851    }
852}
853
854impl<T: PY> PY for Vec<T> {
855    type WithoutGenerics = Vec<Dummy>;
856
857    fn ident() -> String {
858        "list".to_owned()
859    }
860
861    fn name() -> String {
862        format!("list[{}]", T::name())
863    }
864
865    fn inline() -> String {
866        format!("list[{}]", T::inline())
867    }
868
869    fn visit_dependencies(v: &mut impl TypeVisitor)
870    where
871        Self: 'static,
872    {
873        T::visit_dependencies(v);
874    }
875
876    fn visit_generics(v: &mut impl TypeVisitor)
877    where
878        Self: 'static,
879    {
880        T::visit_generics(v);
881        v.visit::<T>();
882    }
883
884    fn decl() -> String {
885        panic!("{} cannot be declared", Self::name())
886    }
887
888    fn decl_concrete() -> String {
889        panic!("{} cannot be declared", Self::name())
890    }
891
892    fn inline_flattened() -> String {
893        panic!("{} cannot be flattened", Self::name())
894    }
895    fn variant_classes_decl() -> String {
896        panic!("{} cannot be declared", Self::name())
897    }
898}
899
900// Arrays longer than this limit will be emitted as Array<T>
901const ARRAY_TUPLE_LIMIT: usize = 64;
902impl<T: PY, const N: usize> PY for [T; N] {
903    type WithoutGenerics = [Dummy; N];
904    fn name() -> String {
905        if N > ARRAY_TUPLE_LIMIT {
906            return Vec::<T>::name();
907        }
908
909        format!(
910            "[{}]",
911            (0..N).map(|_| T::name()).collect::<Box<[_]>>().join(", ")
912        )
913    }
914
915    fn inline() -> String {
916        if N > ARRAY_TUPLE_LIMIT {
917            return Vec::<T>::inline();
918        }
919
920        format!(
921            "[{}]",
922            (0..N).map(|_| T::inline()).collect::<Box<[_]>>().join(", ")
923        )
924    }
925
926    fn visit_dependencies(v: &mut impl TypeVisitor)
927    where
928        Self: 'static,
929    {
930        T::visit_dependencies(v);
931    }
932
933    fn visit_generics(v: &mut impl TypeVisitor)
934    where
935        Self: 'static,
936    {
937        T::visit_generics(v);
938        v.visit::<T>();
939    }
940
941    fn decl() -> String {
942        panic!("{} cannot be declared", Self::name())
943    }
944
945    fn decl_concrete() -> String {
946        panic!("{} cannot be declared", Self::name())
947    }
948
949    fn inline_flattened() -> String {
950        panic!("{} cannot be flattened", Self::name())
951    }
952    fn variant_classes_decl() -> String {
953        panic!("{} cannot be declared", Self::name())
954    }
955}
956
957impl<K: PY, V: PY, H> PY for HashMap<K, V, H> {
958    type WithoutGenerics = HashMap<Dummy, Dummy>;
959
960    fn ident() -> String {
961        panic!()
962    }
963
964    fn name() -> String {
965        format!("{{ [key in {}]?: {} }}", K::name(), V::name()) // TODO update
966    }
967
968    fn inline() -> String {
969        format!("dict[{}, {}]", K::inline(), V::inline())
970    }
971
972    fn visit_dependencies(v: &mut impl TypeVisitor)
973    where
974        Self: 'static,
975    {
976        K::visit_dependencies(v);
977        V::visit_dependencies(v);
978    }
979
980    fn visit_generics(v: &mut impl TypeVisitor)
981    where
982        Self: 'static,
983    {
984        K::visit_generics(v);
985        v.visit::<K>();
986        V::visit_generics(v);
987        v.visit::<V>();
988    }
989
990    fn decl() -> String {
991        panic!("{} cannot be declared", Self::name())
992    }
993
994    fn decl_concrete() -> String {
995        panic!("{} cannot be declared", Self::name())
996    }
997
998    fn inline_flattened() -> String {
999        panic!("{} cannot be flattened", Self::name())
1000    }
1001    fn variant_classes_decl() -> String {
1002        panic!("{} cannot be declared", Self::name())
1003    }
1004}
1005
1006// TODO should not be supported
1007impl<I: PY> PY for Range<I> {
1008    type WithoutGenerics = Range<Dummy>;
1009    fn name() -> String {
1010        format!("{{ start: {}, end: {}, }}", I::name(), I::name())
1011    }
1012
1013    fn visit_dependencies(v: &mut impl TypeVisitor)
1014    where
1015        Self: 'static,
1016    {
1017        I::visit_dependencies(v);
1018    }
1019
1020    fn visit_generics(v: &mut impl TypeVisitor)
1021    where
1022        Self: 'static,
1023    {
1024        I::visit_generics(v);
1025        v.visit::<I>();
1026    }
1027
1028    fn decl() -> String {
1029        panic!("{} cannot be declared", Self::name())
1030    }
1031
1032    fn decl_concrete() -> String {
1033        panic!("{} cannot be declared", Self::name())
1034    }
1035
1036    fn inline() -> String {
1037        panic!("{} cannot be inlined", Self::name())
1038    }
1039
1040    fn inline_flattened() -> String {
1041        panic!("{} cannot be flattened", Self::name())
1042    }
1043    fn variant_classes_decl() -> String {
1044        panic!("{} cannot be declared", Self::name())
1045    }
1046}
1047
1048impl_shadow!(as Range<I>: impl<I: PY> PY for RangeInclusive<I>);
1049impl_shadow!(as Vec<T>: impl<T: PY, H> PY for HashSet<T, H>);
1050impl_shadow!(as Vec<T>: impl<T: PY> PY for BTreeSet<T>);
1051impl_shadow!(as HashMap<K, V>: impl<K: PY, V: PY> PY for BTreeMap<K, V>);
1052impl_shadow!(as Vec<T>: impl<T: PY> PY for [T]);
1053
1054impl_wrapper!(impl<T: PY + ?Sized> PY for &T);
1055impl_wrapper!(impl<T: PY + ?Sized> PY for Box<T>);
1056impl_wrapper!(impl<T: PY + ?Sized> PY for std::sync::Arc<T>);
1057impl_wrapper!(impl<T: PY + ?Sized> PY for std::rc::Rc<T>);
1058impl_wrapper!(impl<'a, T: PY + ToOwned + ?Sized> PY for std::borrow::Cow<'a, T>);
1059impl_wrapper!(impl<T: PY> PY for std::cell::Cell<T>);
1060impl_wrapper!(impl<T: PY> PY for std::cell::RefCell<T>);
1061impl_wrapper!(impl<T: PY> PY for std::sync::Mutex<T>);
1062impl_wrapper!(impl<T: PY> PY for std::sync::RwLock<T>);
1063impl_wrapper!(impl<T: PY + ?Sized> PY for std::sync::Weak<T>);
1064impl_wrapper!(impl<T: PY> PY for std::marker::PhantomData<T>);
1065
1066impl_tuples!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
1067
1068#[cfg(feature = "bigdecimal-impl")]
1069impl_primitives! { bigdecimal::BigDecimal => "str" }
1070
1071#[cfg(feature = "smol_str-impl")]
1072impl_primitives! { smol_str::SmolStr => "str" }
1073
1074#[cfg(feature = "uuid-impl")]
1075impl_primitives! { uuid::Uuid => "str" }
1076
1077#[cfg(feature = "url-impl")]
1078impl_primitives! { url::Url => "str" }
1079
1080#[cfg(feature = "ordered-float-impl")]
1081impl_primitives! { ordered_float::OrderedFloat<f32> => "float" }
1082
1083#[cfg(feature = "ordered-float-impl")]
1084impl_primitives! { ordered_float::OrderedFloat<f64> => "float" }
1085
1086#[cfg(feature = "bson-uuid-impl")]
1087impl_primitives! { bson::oid::ObjectId => "str" }
1088
1089#[cfg(feature = "bson-uuid-impl")]
1090impl_primitives! { bson::Uuid => "str" }
1091
1092#[cfg(feature = "indexmap-impl")]
1093impl_shadow!(as Vec<T>: impl<T: PY> PY for indexmap::IndexSet<T>);
1094
1095#[cfg(feature = "indexmap-impl")]
1096impl_shadow!(as HashMap<K, V>: impl<K: PY, V: PY> PY for indexmap::IndexMap<K, V>);
1097
1098#[cfg(feature = "heapless-impl")]
1099impl_shadow!(as Vec<T>: impl<T: PY, const N: usize> PY for heapless::Vec<T, N>);
1100
1101#[cfg(feature = "semver-impl")]
1102impl_primitives! { semver::Version => "str" }
1103
1104#[cfg(feature = "bytes-impl")]
1105mod bytes {
1106    use super::PY;
1107
1108    impl_shadow!(as Vec<u8>: impl PY for bytes::Bytes);
1109    impl_shadow!(as Vec<u8>: impl PY for bytes::BytesMut);
1110}
1111
1112impl_primitives! {
1113    u8, i8, NonZeroU8, NonZeroI8,
1114    u16, i16, NonZeroU16, NonZeroI16,
1115    u32, i32, NonZeroU32, NonZeroI32,
1116    usize, isize, NonZeroUsize, NonZeroIsize,
1117    u64, i64, NonZeroU64, NonZeroI64,
1118    u128, i128, NonZeroU128, NonZeroI128 => "int",
1119    f32, f64 => "float",
1120    bool => "bool",
1121    char, Path, PathBuf, String, str,
1122    Ipv4Addr, Ipv6Addr, IpAddr, SocketAddrV4, SocketAddrV6, SocketAddr => "str",
1123    () => "None"
1124}
1125
1126#[rustfmt::skip]
1127pub(crate) use impl_primitives;
1128#[rustfmt::skip]
1129pub(crate) use impl_shadow;
1130#[rustfmt::skip]
1131pub(crate) use impl_wrapper;
1132
1133#[doc(hidden)]
1134#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
1135pub struct Dummy;
1136
1137impl std::fmt::Display for Dummy {
1138    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1139        write!(f, "{:?}", self)
1140    }
1141}
1142
1143impl PY for Dummy {
1144    type WithoutGenerics = Self;
1145    fn name() -> String {
1146        "Dummy".to_owned()
1147    }
1148
1149    fn decl() -> String {
1150        panic!("{} cannot be declared", Self::name())
1151    }
1152
1153    fn decl_concrete() -> String {
1154        panic!("{} cannot be declared", Self::name())
1155    }
1156
1157    fn inline() -> String {
1158        panic!("{} cannot be inlined", Self::name())
1159    }
1160
1161    fn inline_flattened() -> String {
1162        panic!("{} cannot be flattened", Self::name())
1163    }
1164
1165    fn variant_classes_decl() -> String {
1166        panic!("{} cannot be declared", Self::name())
1167    }
1168}