postcard_bindgen_core/code_gen/python/
mod.rs

1mod des;
2mod general;
3mod generateable;
4mod ser;
5mod type_checks;
6
7use core::borrow::Borrow;
8
9use convert_case::{Case, Casing};
10use des::{gen_des_functions, gen_deserialize_func, gen_deserializer_code};
11use genco::{lang::python::Python, quote, quote_in, tokens::FormatInto};
12use general::gen_util;
13use generateable::{gen_basic_typings, gen_typings};
14use ser::{gen_ser_functions, gen_serialize_func, gen_serializer_code};
15use type_checks::gen_type_checks;
16
17use crate::{
18    code_gen::import_registry::ImportMode, path::PathBuf, registry::ContainerCollection, Exports,
19};
20
21use super::{
22    import_registry::{ImportItem, Package},
23    utils::{IfBranchedTemplate, TokensBranchedIterExt, TokensIterExt},
24};
25
26const PYTHON_OBJECT_VARIABLE: &str = "v";
27const PYTHON_LOGIC_AND: &str = "and";
28const PYTHON_LOGIC_OR: &str = "or";
29
30type Tokens = genco::lang::python::Tokens;
31
32type VariablePath = super::variable_path::VariablePath<Python>;
33type VariableAccess = super::variable_path::VariableAccess;
34type FieldAccessor<'a> = super::field_accessor::FieldAccessor<'a>;
35type AvailableCheck = super::available_check::AvailableCheck<Python>;
36type ImportRegistry = super::import_registry::ImportRegistry;
37type ExportFile = crate::ExportFile<Python>;
38type FunctionArg = super::function::FunctionArg<Python>;
39type Function = super::function::Function<Python>;
40
41/// Settings for bindings generation.
42///
43/// This enables the possibility to enable or disable serialization, deserialization, runtime type checks
44/// or type script types.
45/// Less code will be generated if an option is off.
46///
47/// By default, only deserialization is enabled. Serialization can be enabled by using [`GenerationSettings::serialization()`].
48/// Deserialization can be disabled with [`GenerationSettings::deserialization()`].
49/// To enable all at once use [`GenerationSettings::enable_all()`].
50#[derive(Debug)]
51pub struct GenerationSettings {
52    ser: bool,
53    des: bool,
54    runtime_type_checks: bool,
55    module_structure: bool,
56}
57
58impl GenerationSettings {
59    /// Constructs [`GenerationSettings`] and enables all options at once.
60    pub fn enable_all() -> Self {
61        Self {
62            ser: true,
63            des: true,
64            runtime_type_checks: true,
65            module_structure: true,
66        }
67    }
68
69    /// Enabling or disabling of serialization code generation.
70    pub fn serialization(mut self, enabled: bool) -> Self {
71        self.ser = enabled;
72        self
73    }
74
75    /// Enabling or disabling of deserialization code generation.
76    pub fn deserialization(mut self, enabled: bool) -> Self {
77        self.des = enabled;
78        self
79    }
80
81    /// Enabling or disabling of runtime type checks code generation.
82    ///
83    /// Disabling this should lead to a speed increase at serialization.
84    pub fn runtime_type_checks(mut self, enabled: bool) -> Self {
85        self.runtime_type_checks = enabled;
86        self
87    }
88
89    /// Enabling or disabling of module structure code generation.
90    ///
91    /// Enabling this will generate the types in the same module structure
92    /// as in rust. Root level types will be in the root of the generated
93    /// module (subpackage types). Types nested in modules will be in subpackages
94    /// (e.g. types.<mod_name>.<type_name>). This avoids name clashes.
95    ///
96    /// Disabling this will generate all types in the root module.
97    pub fn module_structure(mut self, enabled: bool) -> Self {
98        self.module_structure = enabled;
99        self
100    }
101}
102
103impl Default for GenerationSettings {
104    fn default() -> Self {
105        Self {
106            ser: false,
107            des: true,
108            runtime_type_checks: false,
109            module_structure: true,
110        }
111    }
112}
113
114pub fn generate(
115    mut containers: ContainerCollection,
116    gen_settings: impl Borrow<GenerationSettings>,
117    generate_package_name: String,
118) -> Exports<Python> {
119    let generate_package_name = generate_package_name.to_case(Case::Snake);
120    let gen_settings = gen_settings.borrow();
121
122    if !gen_settings.module_structure {
123        containers.flatten();
124    }
125
126    let mut files = Vec::new();
127
128    files.push(ExportFile {
129        content_type: "util".to_owned(),
130        content: gen_util(),
131    });
132
133    files.push(ExportFile {
134        content_type: "basic_types".to_owned(),
135        content: gen_basic_typings(),
136    });
137
138    files.extend(gen_typings(&containers, generate_package_name.clone()));
139
140    if gen_settings.runtime_type_checks {
141        let type_checks = gen_type_checks(containers.all_containers());
142
143        let type_checks = quote! {
144            from .util import *
145            from .types import *
146
147            $type_checks
148        };
149
150        files.push(ExportFile {
151            content_type: "runtime_checks".to_owned(),
152            content: type_checks,
153        });
154    }
155
156    if gen_settings.ser {
157        let serializer_code = gen_serializer_code();
158        let ser_code = quote! {
159            from .types import *
160            from .util import *
161            from .serializer import Serializer
162
163            $(gen_ser_functions(containers.all_containers()))
164
165            $(gen_serialize_func(containers.all_containers(), gen_settings.runtime_type_checks))
166        };
167
168        files.push(ExportFile {
169            content_type: "serializer".to_owned(),
170            content: serializer_code,
171        });
172
173        files.push(ExportFile {
174            content_type: "ser".to_owned(),
175            content: ser_code,
176        });
177    }
178
179    if gen_settings.des {
180        let deserializer_code = gen_deserializer_code();
181        let des_code = quote! {
182            from typing import TypeVar, Type, cast, Tuple
183
184            from .types import *
185            from .util import *
186            from .deserializer import Deserializer
187
188            $(gen_des_functions(containers.all_containers()))
189
190            $(gen_deserialize_func(containers.all_containers()))
191        };
192
193        files.push(ExportFile {
194            content_type: "deserializer".to_owned(),
195            content: deserializer_code,
196        });
197
198        files.push(ExportFile {
199            content_type: "des".to_owned(),
200            content: des_code,
201        });
202    }
203
204    let mut import_registry = ImportRegistry::new(generate_package_name);
205    import_registry.push(Package::Relative("types".into()), ImportItem::All);
206    import_registry.push(Package::Relative("basic_types".into()), ImportItem::All);
207
208    if gen_settings.des {
209        import_registry.push(
210            Package::Relative("des".into()),
211            ImportItem::Single("deserialize".into()),
212        );
213    }
214
215    if gen_settings.ser {
216        import_registry.push(
217            Package::Relative("ser".into()),
218            ImportItem::Single("serialize".into()),
219        );
220    }
221
222    files.push(ExportFile {
223        content_type: "__init__".to_owned(),
224        content: quote!($import_registry),
225    });
226
227    Exports { files }
228}
229
230impl<I, F> TokensIterExt<Python, F> for I
231where
232    I: Iterator<Item = F>,
233    F: FormatInto<Python>,
234{
235    const LOGICAL_AND: &'static str = PYTHON_LOGIC_AND;
236    const LOGICAL_OR: &'static str = PYTHON_LOGIC_OR;
237}
238
239pub(super) struct BranchedTemplate;
240
241impl IfBranchedTemplate<Python> for BranchedTemplate {
242    const IF_BRANCH: &'static str = "if";
243    const IF_ELSE_BRANCH: &'static str = "elif";
244    const ELSE_BRANCH: &'static str = "else";
245
246    fn push_condition(tokens: &mut Tokens, condition: impl FormatInto<Python>) {
247        tokens.append(condition)
248    }
249
250    fn push_condition_block(tokens: &mut Tokens, body: impl FormatInto<Python>) {
251        tokens.append(":");
252        tokens.indent();
253        tokens.append(body);
254        tokens.unindent();
255    }
256}
257
258impl<I> TokensBranchedIterExt<Python> for I
259where
260    I: Iterator<Item = (Option<Tokens>, Tokens)>,
261{
262    type Template = BranchedTemplate;
263}
264
265impl FormatInto<Python> for FieldAccessor<'_> {
266    fn format_into(self, tokens: &mut Tokens) {
267        quote_in! { *tokens =>
268            $(match self {
269                Self::Array | Self::None => (),
270                Self::Object(n) => $n = $[' '],
271            })
272        }
273    }
274}
275
276impl FormatInto<Python> for VariablePath {
277    fn format_into(self, tokens: &mut genco::Tokens<Python>) {
278        quote_in! { *tokens =>
279            $(self.start_variable)
280        }
281        self.parts
282            .into_iter()
283            .for_each(|part| part.format_into(tokens))
284    }
285}
286
287impl Default for VariablePath {
288    fn default() -> Self {
289        Self::new(PYTHON_OBJECT_VARIABLE.to_owned())
290    }
291}
292
293impl FormatInto<Python> for VariableAccess {
294    fn format_into(self, tokens: &mut genco::Tokens<Python>) {
295        quote_in! { *tokens =>
296            $(match self {
297                Self::Indexed(index) => [$index],
298                Self::Field(name) => .$name,
299            })
300        }
301    }
302}
303
304impl FormatInto<Python> for AvailableCheck {
305    fn format_into(self, tokens: &mut Tokens) {
306        quote_in! { *tokens =>
307            $(match self {
308                AvailableCheck::Object(..) => (),
309                AvailableCheck::None => ()
310            })
311        }
312    }
313}
314
315impl FormatInto<Python> for ImportRegistry {
316    fn format_into(self, tokens: &mut Tokens) {
317        let (base_path, items) = self.into_items_sorted();
318        for (package, imports) in items {
319            let joiner = ".";
320            let package = match package {
321                Package::Relative(path) => format!(".{}", path.into_path(joiner)),
322                Package::Extern(path) => path.into_path(joiner).to_string(),
323                Package::Intern(mut path) => {
324                    if !path.is_empty() {
325                        path.push_front(base_path.as_str());
326                        path.into_path(joiner).to_string()
327                    } else {
328                        PathBuf::new()
329                            .join(base_path.as_str())
330                            .into_path(joiner)
331                            .to_string()
332                    }
333                }
334                Package::Root => base_path.to_owned(),
335            };
336
337            quote_in!(*tokens=> from $(package) import);
338            tokens.space();
339
340            match imports {
341                ImportMode::All => quote_in!(*tokens=> *),
342                ImportMode::Single(items) => {
343                    let items = items.iter().map(|i| {
344                        if let Some(alias) = &i.alias {
345                            quote!($(&i.name) as $alias)
346                        } else {
347                            quote!($(&i.name))
348                        }
349                    });
350                    quote_in!(*tokens=> $(for part in items join (, ) => $part))
351                }
352            }
353
354            tokens.push();
355        }
356    }
357}
358
359impl FormatInto<Python> for FunctionArg {
360    fn format_into(self, tokens: &mut Tokens) {
361        if let Some(r#type) = self.r#type {
362            quote_in! { *tokens =>
363                $(self.name): $r#type
364            }
365        } else {
366            quote_in! { *tokens =>
367                $(self.name)
368            }
369        }
370    }
371}
372
373impl FormatInto<Python> for Function {
374    fn format_into(self, tokens: &mut Tokens) {
375        let doc_string = self.doc_string.map(|doc_string| {
376            let mut tokens = Tokens::new();
377
378            tokens.append("\"\"\"");
379            tokens.append(
380                doc_string
381                    .lines()
382                    .enumerate()
383                    .map(|(i, f)| {
384                        if i > 0 {
385                            format!("    {}", f)
386                        } else {
387                            f.to_string()
388                        }
389                    })
390                    .collect::<Vec<_>>()
391                    .join("\n"),
392            );
393            tokens.append("\"\"\"");
394            tokens
395        });
396
397        let return_type = self.return_type.map(|r| quote!($(" ")-> $r));
398        quote_in! { *tokens =>
399            def $(self.name)($(for arg in self.args join (, ) => $arg))$return_type:
400                $(doc_string)
401                $(self.body)
402        }
403    }
404}
405
406#[cfg(test)]
407mod test {
408    use genco::tokens::FormatInto;
409
410    use super::Tokens;
411
412    #[test]
413    fn test_import_registry_format() {
414        use super::{ImportItem, ImportRegistry, Package};
415
416        let mut import_registry = ImportRegistry::new("package".to_owned());
417        import_registry.push(
418            Package::Relative("basic_types".into()),
419            ImportItem::Aliased {
420                item_name: "A".into(),
421                alias: "A__A".into(),
422            },
423        );
424        import_registry.push(
425            Package::Intern("des".into()),
426            ImportItem::Single("deserialize".into()),
427        );
428        import_registry.push(
429            Package::Extern("ser".into()),
430            ImportItem::Single("serialize".into()),
431        );
432        import_registry.push(Package::Relative("types".into()), ImportItem::All);
433
434        let mut tokens = Tokens::new();
435        import_registry.format_into(&mut tokens);
436
437        assert_eq!(
438            tokens.to_file_string().unwrap(),
439            format!(
440                r#"from ser import serialize
441from package.des import deserialize
442from .basic_types import A as A__A
443from .types import *
444"#
445            )
446        );
447    }
448}