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}