summaryrefslogtreecommitdiffstats
path: root/third_party/rust/uniffi_bindgen/src/bindings/python
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/uniffi_bindgen/src/bindings/python')
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/callback_interface.rs33
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs122
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/custom.rs29
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/enum_.rs41
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/error.rs33
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs29
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/miscellany.rs36
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs448
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/object.rs33
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/primitives.rs69
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/record.rs33
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/mod.rs40
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py16
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py73
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py104
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/CustomType.py52
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py93
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py75
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py7
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Helpers.py67
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py27
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py39
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py74
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py21
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py45
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py23
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py190
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py19
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/StringHelper.py25
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py30
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py13
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/Types.py93
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py8
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/macros.py101
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/templates/wrapper.py71
-rw-r--r--third_party/rust/uniffi_bindgen/src/bindings/python/test.rs62
46 files changed, 2365 insertions, 0 deletions
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/callback_interface.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/callback_interface.rs
new file mode 100644
index 0000000000..9359fbaeae
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/callback_interface.rs
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::backend::{CodeOracle, CodeType, Literal};
+
+pub struct CallbackInterfaceCodeType {
+ id: String,
+}
+
+impl CallbackInterfaceCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for CallbackInterfaceCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.class_name(&self.id)
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("CallbackInterface{}", self.id)
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, _literal: &Literal) -> String {
+ unreachable!();
+ }
+
+ fn coerce(&self, _oracle: &dyn CodeOracle, nm: &str) -> String {
+ nm.to_string()
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs
new file mode 100644
index 0000000000..80b1da040d
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/compounds.rs
@@ -0,0 +1,122 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::backend::{CodeOracle, CodeType, Literal, TypeIdentifier};
+
+pub struct OptionalCodeType {
+ inner: TypeIdentifier,
+}
+
+impl OptionalCodeType {
+ pub fn new(inner: TypeIdentifier) -> Self {
+ Self { inner }
+ }
+}
+
+impl CodeType for OptionalCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.find(&self.inner).type_label(oracle)
+ }
+
+ fn canonical_name(&self, oracle: &dyn CodeOracle) -> String {
+ format!(
+ "Optional{}",
+ oracle.find(&self.inner).canonical_name(oracle),
+ )
+ }
+
+ fn literal(&self, oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ match literal {
+ Literal::Null => "None".into(),
+ _ => oracle.find(&self.inner).literal(oracle, literal),
+ }
+ }
+
+ fn coerce(&self, oracle: &dyn CodeOracle, nm: &str) -> String {
+ format!(
+ "(None if {} is None else {})",
+ nm,
+ oracle.find(&self.inner).coerce(oracle, nm)
+ )
+ }
+}
+
+pub struct SequenceCodeType {
+ inner: TypeIdentifier,
+}
+
+impl SequenceCodeType {
+ pub fn new(inner: TypeIdentifier) -> Self {
+ Self { inner }
+ }
+}
+
+impl CodeType for SequenceCodeType {
+ fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+ "list".to_string()
+ }
+
+ fn canonical_name(&self, oracle: &dyn CodeOracle) -> String {
+ format!(
+ "Sequence{}",
+ oracle.find(&self.inner).canonical_name(oracle),
+ )
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ match literal {
+ Literal::EmptySequence => "[]".into(),
+ _ => unimplemented!(),
+ }
+ }
+
+ fn coerce(&self, oracle: &dyn CodeOracle, nm: &str) -> String {
+ format!(
+ "list({} for x in {})",
+ oracle.find(&self.inner).coerce(oracle, "x"),
+ nm
+ )
+ }
+}
+
+pub struct MapCodeType {
+ key: TypeIdentifier,
+ value: TypeIdentifier,
+}
+
+impl MapCodeType {
+ pub fn new(key: TypeIdentifier, value: TypeIdentifier) -> Self {
+ Self { key, value }
+ }
+}
+
+impl CodeType for MapCodeType {
+ fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+ "dict".to_string()
+ }
+
+ fn canonical_name(&self, oracle: &dyn CodeOracle) -> String {
+ format!(
+ "Map{}{}",
+ oracle.find(&self.key).canonical_name(oracle),
+ oracle.find(&self.value).canonical_name(oracle),
+ )
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ match literal {
+ Literal::EmptyMap => "{}".into(),
+ _ => unimplemented!(),
+ }
+ }
+
+ fn coerce(&self, oracle: &dyn CodeOracle, nm: &str) -> String {
+ format!(
+ "dict(({}, {}) for (k, v) in {}.items())",
+ self.key.coerce(oracle, "k"),
+ self.value.coerce(oracle, "v"),
+ nm
+ )
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/custom.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/custom.rs
new file mode 100644
index 0000000000..d53a946947
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/custom.rs
@@ -0,0 +1,29 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::backend::{CodeOracle, CodeType};
+
+pub struct CustomCodeType {
+ name: String,
+}
+
+impl CustomCodeType {
+ pub fn new(name: String) -> Self {
+ Self { name }
+ }
+}
+
+impl CodeType for CustomCodeType {
+ fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+ self.name.clone()
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.name)
+ }
+
+ fn coerce(&self, _oracle: &dyn CodeOracle, nm: &str) -> String {
+ nm.to_string()
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/enum_.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/enum_.rs
new file mode 100644
index 0000000000..9a86076770
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/enum_.rs
@@ -0,0 +1,41 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::backend::{CodeOracle, CodeType, Literal};
+
+pub struct EnumCodeType {
+ id: String,
+}
+
+impl EnumCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for EnumCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.class_name(&self.id)
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.id)
+ }
+
+ fn literal(&self, oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ if let Literal::Enum(v, _) = literal {
+ format!(
+ "{}.{}",
+ self.type_label(oracle),
+ oracle.enum_variant_name(v)
+ )
+ } else {
+ unreachable!();
+ }
+ }
+
+ fn coerce(&self, _oracle: &dyn CodeOracle, nm: &str) -> String {
+ nm.to_string()
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/error.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/error.rs
new file mode 100644
index 0000000000..f1567281f2
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/error.rs
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::backend::{CodeOracle, CodeType, Literal};
+
+pub struct ErrorCodeType {
+ id: String,
+}
+
+impl ErrorCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for ErrorCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.class_name(&self.id)
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.id)
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, _literal: &Literal) -> String {
+ unreachable!();
+ }
+
+ fn coerce(&self, _oracle: &dyn CodeOracle, nm: &str) -> String {
+ nm.to_string()
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs
new file mode 100644
index 0000000000..1739d84cec
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/external.rs
@@ -0,0 +1,29 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::backend::{CodeOracle, CodeType};
+
+pub struct ExternalCodeType {
+ name: String,
+}
+
+impl ExternalCodeType {
+ pub fn new(name: String) -> Self {
+ Self { name }
+ }
+}
+
+impl CodeType for ExternalCodeType {
+ fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+ self.name.clone()
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.name)
+ }
+
+ fn coerce(&self, _oracle: &dyn CodeOracle, nm: &str) -> String {
+ nm.into()
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/miscellany.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/miscellany.rs
new file mode 100644
index 0000000000..c885428bc3
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/miscellany.rs
@@ -0,0 +1,36 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::backend::{CodeOracle, CodeType, Literal};
+use paste::paste;
+
+macro_rules! impl_code_type_for_miscellany {
+ ($T:ty, $canonical_name:literal) => {
+ paste! {
+ pub struct $T;
+
+ impl CodeType for $T {
+ fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("{}", $canonical_name)
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("{}", $canonical_name)
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, _literal: &Literal) -> String {
+ unreachable!()
+ }
+
+ fn coerce(&self, _oracle: &dyn CodeOracle, nm: &str) -> String {
+ nm.to_string()
+ }
+ }
+ }
+ };
+}
+
+impl_code_type_for_miscellany!(TimestampCodeType, "Timestamp");
+
+impl_code_type_for_miscellany!(DurationCodeType, "Duration");
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs
new file mode 100644
index 0000000000..0b2218402a
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/mod.rs
@@ -0,0 +1,448 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use anyhow::{Context, Result};
+use askama::Template;
+use heck::{ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase};
+use once_cell::sync::Lazy;
+use serde::{Deserialize, Serialize};
+use std::borrow::Borrow;
+use std::cell::RefCell;
+use std::collections::{BTreeSet, HashMap, HashSet};
+
+use crate::backend::{CodeOracle, CodeType, TemplateExpression, TypeIdentifier};
+use crate::interface::*;
+use crate::MergeWith;
+
+mod callback_interface;
+mod compounds;
+mod custom;
+mod enum_;
+mod error;
+mod external;
+mod miscellany;
+mod object;
+mod primitives;
+mod record;
+
+// Taken from Python's `keyword.py` module.
+static KEYWORDS: Lazy<HashSet<String>> = Lazy::new(|| {
+ let kwlist = vec![
+ "False",
+ "None",
+ "True",
+ "__peg_parser__",
+ "and",
+ "as",
+ "assert",
+ "async",
+ "await",
+ "break",
+ "class",
+ "continue",
+ "def",
+ "del",
+ "elif",
+ "else",
+ "except",
+ "finally",
+ "for",
+ "from",
+ "global",
+ "if",
+ "import",
+ "in",
+ "is",
+ "lambda",
+ "nonlocal",
+ "not",
+ "or",
+ "pass",
+ "raise",
+ "return",
+ "try",
+ "while",
+ "with",
+ "yield",
+ ];
+ HashSet::from_iter(kwlist.into_iter().map(|s| s.to_string()))
+});
+
+// Config options to customize the generated python.
+#[derive(Debug, Clone, Default, Serialize, Deserialize)]
+pub struct Config {
+ cdylib_name: Option<String>,
+ #[serde(default)]
+ custom_types: HashMap<String, CustomTypeConfig>,
+}
+
+#[derive(Debug, Clone, Default, Serialize, Deserialize)]
+pub struct CustomTypeConfig {
+ // This `CustomTypeConfig` doesn't have a `type_name` like the others -- which is why we have
+ // separate structs rather than a shared one.
+ imports: Option<Vec<String>>,
+ into_custom: TemplateExpression,
+ from_custom: TemplateExpression,
+}
+
+impl Config {
+ pub fn cdylib_name(&self) -> String {
+ if let Some(cdylib_name) = &self.cdylib_name {
+ cdylib_name.clone()
+ } else {
+ "uniffi".into()
+ }
+ }
+}
+
+impl From<&ComponentInterface> for Config {
+ fn from(ci: &ComponentInterface) -> Self {
+ Config {
+ cdylib_name: Some(format!("uniffi_{}", ci.namespace())),
+ custom_types: HashMap::new(),
+ }
+ }
+}
+
+impl MergeWith for Config {
+ fn merge_with(&self, other: &Self) -> Self {
+ Config {
+ cdylib_name: self.cdylib_name.merge_with(&other.cdylib_name),
+ custom_types: self.custom_types.merge_with(&other.custom_types),
+ }
+ }
+}
+
+// Generate python bindings for the given ComponentInterface, as a string.
+pub fn generate_python_bindings(config: &Config, ci: &ComponentInterface) -> Result<String> {
+ PythonWrapper::new(config.clone(), ci)
+ .render()
+ .context("failed to render python bindings")
+}
+
+/// A struct to record a Python import statement.
+#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
+pub enum ImportRequirement {
+ /// A simple module import.
+ Module { mod_name: String },
+ /// A single symbol from a module.
+ Symbol {
+ mod_name: String,
+ symbol_name: String,
+ },
+ /// A single symbol from a module with the specified local name.
+ SymbolAs {
+ mod_name: String,
+ symbol_name: String,
+ as_name: String,
+ },
+}
+
+impl ImportRequirement {
+ /// Render the Python import statement.
+ fn render(&self) -> String {
+ match &self {
+ ImportRequirement::Module { mod_name } => format!("import {mod_name}"),
+ ImportRequirement::Symbol {
+ mod_name,
+ symbol_name,
+ } => format!("from {mod_name} import {symbol_name}"),
+ ImportRequirement::SymbolAs {
+ mod_name,
+ symbol_name,
+ as_name,
+ } => format!("from {mod_name} import {symbol_name} as {as_name}"),
+ }
+ }
+}
+
+/// Renders Python helper code for all types
+///
+/// This template is a bit different than others in that it stores internal state from the render
+/// process. Make sure to only call `render()` once.
+#[derive(Template)]
+#[template(syntax = "py", escape = "none", path = "Types.py")]
+pub struct TypeRenderer<'a> {
+ python_config: &'a Config,
+ ci: &'a ComponentInterface,
+ // Track included modules for the `include_once()` macro
+ include_once_names: RefCell<HashSet<String>>,
+ // Track imports added with the `add_import()` macro
+ imports: RefCell<BTreeSet<ImportRequirement>>,
+}
+
+impl<'a> TypeRenderer<'a> {
+ fn new(python_config: &'a Config, ci: &'a ComponentInterface) -> Self {
+ Self {
+ python_config,
+ ci,
+ include_once_names: RefCell::new(HashSet::new()),
+ imports: RefCell::new(BTreeSet::new()),
+ }
+ }
+
+ // The following methods are used by the `Types.py` macros.
+
+ // Helper for the including a template, but only once.
+ //
+ // The first time this is called with a name it will return true, indicating that we should
+ // include the template. Subsequent calls will return false.
+ fn include_once_check(&self, name: &str) -> bool {
+ self.include_once_names
+ .borrow_mut()
+ .insert(name.to_string())
+ }
+
+ // Helper to add an import statement
+ //
+ // Call this inside your template to cause an import statement to be added at the top of the
+ // file. Imports will be sorted and de-deuped.
+ //
+ // Returns an empty string so that it can be used inside an askama `{{ }}` block.
+ fn add_import(&self, name: &str) -> &str {
+ self.imports.borrow_mut().insert(ImportRequirement::Module {
+ mod_name: name.to_owned(),
+ });
+ ""
+ }
+
+ // Like add_import, but arranges for `from module import name`.
+ fn add_import_of(&self, mod_name: &str, name: &str) -> &str {
+ self.imports.borrow_mut().insert(ImportRequirement::Symbol {
+ mod_name: mod_name.to_owned(),
+ symbol_name: name.to_owned(),
+ });
+ ""
+ }
+
+ // Like add_import, but arranges for `from module import name as other`.
+ fn add_import_of_as(&self, mod_name: &str, symbol_name: &str, as_name: &str) -> &str {
+ self.imports
+ .borrow_mut()
+ .insert(ImportRequirement::SymbolAs {
+ mod_name: mod_name.to_owned(),
+ symbol_name: symbol_name.to_owned(),
+ as_name: as_name.to_owned(),
+ });
+ ""
+ }
+}
+
+#[derive(Template)]
+#[template(syntax = "py", escape = "none", path = "wrapper.py")]
+pub struct PythonWrapper<'a> {
+ ci: &'a ComponentInterface,
+ config: Config,
+ type_helper_code: String,
+ type_imports: BTreeSet<ImportRequirement>,
+}
+impl<'a> PythonWrapper<'a> {
+ pub fn new(config: Config, ci: &'a ComponentInterface) -> Self {
+ let type_renderer = TypeRenderer::new(&config, ci);
+ let type_helper_code = type_renderer.render().unwrap();
+ let type_imports = type_renderer.imports.into_inner();
+ Self {
+ config,
+ ci,
+ type_helper_code,
+ type_imports,
+ }
+ }
+
+ pub fn imports(&self) -> Vec<ImportRequirement> {
+ self.type_imports.iter().cloned().collect()
+ }
+}
+
+fn fixup_keyword(name: String) -> String {
+ if KEYWORDS.contains(&name) {
+ format!("_{name}")
+ } else {
+ name
+ }
+}
+
+#[derive(Clone, Default)]
+pub struct PythonCodeOracle;
+
+impl PythonCodeOracle {
+ // Map `Type` instances to a `Box<dyn CodeType>` for that type.
+ //
+ // There is a companion match in `templates/Types.py` which performs a similar function for the
+ // template code.
+ //
+ // - When adding additional types here, make sure to also add a match arm to the `Types.py` template.
+ // - To keep things manageable, let's try to limit ourselves to these 2 mega-matches
+ fn create_code_type(&self, type_: TypeIdentifier) -> Box<dyn CodeType> {
+ match type_ {
+ Type::UInt8 => Box::new(primitives::UInt8CodeType),
+ Type::Int8 => Box::new(primitives::Int8CodeType),
+ Type::UInt16 => Box::new(primitives::UInt16CodeType),
+ Type::Int16 => Box::new(primitives::Int16CodeType),
+ Type::UInt32 => Box::new(primitives::UInt32CodeType),
+ Type::Int32 => Box::new(primitives::Int32CodeType),
+ Type::UInt64 => Box::new(primitives::UInt64CodeType),
+ Type::Int64 => Box::new(primitives::Int64CodeType),
+ Type::Float32 => Box::new(primitives::Float32CodeType),
+ Type::Float64 => Box::new(primitives::Float64CodeType),
+ Type::Boolean => Box::new(primitives::BooleanCodeType),
+ Type::String => Box::new(primitives::StringCodeType),
+
+ Type::Timestamp => Box::new(miscellany::TimestampCodeType),
+ Type::Duration => Box::new(miscellany::DurationCodeType),
+
+ Type::Enum(id) => Box::new(enum_::EnumCodeType::new(id)),
+ Type::Object(id) => Box::new(object::ObjectCodeType::new(id)),
+ Type::Record(id) => Box::new(record::RecordCodeType::new(id)),
+ Type::Error(id) => Box::new(error::ErrorCodeType::new(id)),
+ Type::CallbackInterface(id) => {
+ Box::new(callback_interface::CallbackInterfaceCodeType::new(id))
+ }
+
+ Type::Optional(inner) => Box::new(compounds::OptionalCodeType::new(*inner)),
+ Type::Sequence(inner) => Box::new(compounds::SequenceCodeType::new(*inner)),
+ Type::Map(key, value) => Box::new(compounds::MapCodeType::new(*key, *value)),
+ Type::External { name, .. } => Box::new(external::ExternalCodeType::new(name)),
+ Type::Custom { name, .. } => Box::new(custom::CustomCodeType::new(name)),
+ Type::Unresolved { name } => {
+ unreachable!("Type `{name}` must be resolved before calling create_code_type")
+ }
+ }
+ }
+}
+
+impl CodeOracle for PythonCodeOracle {
+ fn find(&self, type_: &TypeIdentifier) -> Box<dyn CodeType> {
+ self.create_code_type(type_.clone())
+ }
+
+ /// Get the idiomatic Python rendering of a class name (for enums, records, errors, etc).
+ fn class_name(&self, nm: &str) -> String {
+ fixup_keyword(nm.to_string().to_upper_camel_case())
+ }
+
+ /// Get the idiomatic Python rendering of a function name.
+ fn fn_name(&self, nm: &str) -> String {
+ fixup_keyword(nm.to_string().to_snake_case())
+ }
+
+ /// Get the idiomatic Python rendering of a variable name.
+ fn var_name(&self, nm: &str) -> String {
+ fixup_keyword(nm.to_string().to_snake_case())
+ }
+
+ /// Get the idiomatic Python rendering of an individual enum variant.
+ fn enum_variant_name(&self, nm: &str) -> String {
+ fixup_keyword(nm.to_string().to_shouty_snake_case())
+ }
+
+ /// Get the idiomatic Python rendering of an exception name
+ /// This replaces "Error" at the end of the name with "Exception".
+ fn error_name(&self, nm: &str) -> String {
+ let name = fixup_keyword(self.class_name(nm));
+ match name.strip_suffix("Error") {
+ None => name,
+ Some(stripped) => format!("{stripped}Exception"),
+ }
+ }
+
+ fn ffi_type_label(&self, ffi_type: &FfiType) -> String {
+ match ffi_type {
+ FfiType::Int8 => "ctypes.c_int8".to_string(),
+ FfiType::UInt8 => "ctypes.c_uint8".to_string(),
+ FfiType::Int16 => "ctypes.c_int16".to_string(),
+ FfiType::UInt16 => "ctypes.c_uint16".to_string(),
+ FfiType::Int32 => "ctypes.c_int32".to_string(),
+ FfiType::UInt32 => "ctypes.c_uint32".to_string(),
+ FfiType::Int64 => "ctypes.c_int64".to_string(),
+ FfiType::UInt64 => "ctypes.c_uint64".to_string(),
+ FfiType::Float32 => "ctypes.c_float".to_string(),
+ FfiType::Float64 => "ctypes.c_double".to_string(),
+ FfiType::RustArcPtr(_) => "ctypes.c_void_p".to_string(),
+ FfiType::RustBuffer(maybe_suffix) => match maybe_suffix {
+ Some(suffix) => format!("RustBuffer{}", suffix),
+ None => "RustBuffer".to_string(),
+ },
+ FfiType::ForeignBytes => "ForeignBytes".to_string(),
+ FfiType::ForeignCallback => "FOREIGN_CALLBACK_T".to_string(),
+ }
+ }
+}
+
+pub mod filters {
+ use super::*;
+
+ fn oracle() -> &'static PythonCodeOracle {
+ &PythonCodeOracle
+ }
+
+ pub fn type_name(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ let oracle = oracle();
+ Ok(codetype.type_label(oracle))
+ }
+
+ pub fn ffi_converter_name(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ let oracle = oracle();
+ Ok(codetype.ffi_converter_name(oracle))
+ }
+
+ pub fn canonical_name(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ let oracle = oracle();
+ Ok(codetype.canonical_name(oracle))
+ }
+
+ pub fn lift_fn(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(format!("{}.lift", ffi_converter_name(codetype)?))
+ }
+
+ pub fn lower_fn(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(format!("{}.lower", ffi_converter_name(codetype)?))
+ }
+
+ pub fn read_fn(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(format!("{}.read", ffi_converter_name(codetype)?))
+ }
+
+ pub fn write_fn(codetype: &impl CodeType) -> Result<String, askama::Error> {
+ Ok(format!("{}.write", ffi_converter_name(codetype)?))
+ }
+
+ pub fn literal_py(
+ literal: &Literal,
+ codetype: &impl CodeType,
+ ) -> Result<String, askama::Error> {
+ let oracle = oracle();
+ Ok(codetype.literal(oracle, literal))
+ }
+
+ /// Get the Python syntax for representing a given low-level `FfiType`.
+ pub fn ffi_type_name(type_: &FfiType) -> Result<String, askama::Error> {
+ Ok(oracle().ffi_type_label(type_))
+ }
+
+ /// Get the idiomatic Python rendering of a class name (for enums, records, errors, etc).
+ pub fn class_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(oracle().class_name(nm))
+ }
+
+ /// Get the idiomatic Python rendering of a function name.
+ pub fn fn_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(oracle().fn_name(nm))
+ }
+
+ /// Get the idiomatic Python rendering of a variable name.
+ pub fn var_name(nm: &str) -> Result<String, askama::Error> {
+ Ok(oracle().var_name(nm))
+ }
+
+ /// Get the idiomatic Python rendering of an individual enum variant.
+ pub fn enum_variant_py(nm: &str) -> Result<String, askama::Error> {
+ Ok(oracle().enum_variant_name(nm))
+ }
+
+ pub fn coerce_py(nm: &str, type_: &Type) -> Result<String, askama::Error> {
+ let oracle = oracle();
+ Ok(oracle.find(type_).coerce(oracle, nm))
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/object.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/object.rs
new file mode 100644
index 0000000000..7744865ec0
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/object.rs
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::backend::{CodeOracle, CodeType, Literal};
+
+pub struct ObjectCodeType {
+ id: String,
+}
+
+impl ObjectCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for ObjectCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.class_name(&self.id)
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.id)
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, _literal: &Literal) -> String {
+ unreachable!();
+ }
+
+ fn coerce(&self, _oracle: &dyn CodeOracle, nm: &str) -> String {
+ nm.to_string()
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/primitives.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/primitives.rs
new file mode 100644
index 0000000000..de7918fb16
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/primitives.rs
@@ -0,0 +1,69 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::backend::{CodeOracle, CodeType, Literal};
+use crate::interface::Radix;
+use paste::paste;
+
+fn render_literal(_oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ match literal {
+ Literal::Boolean(v) => {
+ if *v {
+ "True".into()
+ } else {
+ "False".into()
+ }
+ }
+ Literal::String(s) => format!("\"{s}\""),
+ // https://docs.python.org/3/reference/lexical_analysis.html#integer-literals
+ Literal::Int(i, radix, _) => match radix {
+ Radix::Octal => format!("int(0o{i:o})"),
+ Radix::Decimal => format!("{i}"),
+ Radix::Hexadecimal => format!("{i:#x}"),
+ },
+ Literal::UInt(i, radix, _) => match radix {
+ Radix::Octal => format!("0o{i:o}"),
+ Radix::Decimal => format!("{i}"),
+ Radix::Hexadecimal => format!("{i:#x}"),
+ },
+ Literal::Float(string, _type_) => string.clone(),
+
+ _ => unreachable!("Literal"),
+ }
+}
+
+macro_rules! impl_code_type_for_primitive {
+ ($T:ty, $class_name:literal, $coerce_code:expr) => {
+ paste! {
+ pub struct $T;
+
+ impl CodeType for $T {
+ fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+ $class_name.into()
+ }
+
+ fn literal(&self, oracle: &dyn CodeOracle, literal: &Literal) -> String {
+ render_literal(oracle, &literal)
+ }
+
+ fn coerce(&self, _oracle: &dyn CodeOracle, nm: &str) -> String {
+ format!($coerce_code, nm)
+ }
+ }
+ }
+ };
+}
+
+impl_code_type_for_primitive!(BooleanCodeType, "Bool", "bool({})");
+impl_code_type_for_primitive!(StringCodeType, "String", "{}");
+impl_code_type_for_primitive!(Int8CodeType, "Int8", "int({})");
+impl_code_type_for_primitive!(Int16CodeType, "Int16", "int({})");
+impl_code_type_for_primitive!(Int32CodeType, "Int32", "int({})");
+impl_code_type_for_primitive!(Int64CodeType, "Int64", "int({})");
+impl_code_type_for_primitive!(UInt8CodeType, "UInt8", "int({})");
+impl_code_type_for_primitive!(UInt16CodeType, "UInt16", "int({})");
+impl_code_type_for_primitive!(UInt32CodeType, "UInt32", "int({})");
+impl_code_type_for_primitive!(UInt64CodeType, "UInt64", "int({})");
+impl_code_type_for_primitive!(Float32CodeType, "Float", "float({})");
+impl_code_type_for_primitive!(Float64CodeType, "Double", "float({})");
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/record.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/record.rs
new file mode 100644
index 0000000000..1dba8e49fe
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/gen_python/record.rs
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::backend::{CodeOracle, CodeType, Literal};
+
+pub struct RecordCodeType {
+ id: String,
+}
+
+impl RecordCodeType {
+ pub fn new(id: String) -> Self {
+ Self { id }
+ }
+}
+
+impl CodeType for RecordCodeType {
+ fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+ oracle.class_name(&self.id)
+ }
+
+ fn canonical_name(&self, _oracle: &dyn CodeOracle) -> String {
+ format!("Type{}", self.id)
+ }
+
+ fn literal(&self, _oracle: &dyn CodeOracle, _literal: &Literal) -> String {
+ unreachable!();
+ }
+
+ fn coerce(&self, _oracle: &dyn CodeOracle, nm: &str) -> String {
+ nm.to_string()
+ }
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/mod.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/mod.rs
new file mode 100644
index 0000000000..3a1cfaa78d
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/mod.rs
@@ -0,0 +1,40 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::{io::Write, process::Command};
+
+use anyhow::Result;
+use camino::Utf8Path;
+use fs_err::File;
+
+pub mod gen_python;
+mod test;
+
+use super::super::interface::ComponentInterface;
+pub use gen_python::{generate_python_bindings, Config};
+pub use test::run_test;
+
+// Generate python bindings for the given ComponentInterface, in the given output directory.
+pub fn write_bindings(
+ config: &Config,
+ ci: &ComponentInterface,
+ out_dir: &Utf8Path,
+ try_format_code: bool,
+) -> Result<()> {
+ let py_file = out_dir.join(format!("{}.py", ci.namespace()));
+ let mut f = File::create(&py_file)?;
+ write!(f, "{}", generate_python_bindings(config, ci)?)?;
+
+ if try_format_code {
+ if let Err(e) = Command::new("yapf").arg(&py_file).output() {
+ println!(
+ "Warning: Unable to auto-format {} using yapf: {:?}",
+ py_file.file_name().unwrap(),
+ e
+ )
+ }
+ }
+
+ Ok(())
+}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py
new file mode 100644
index 0000000000..d7b64b91f7
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/BooleanHelper.py
@@ -0,0 +1,16 @@
+class FfiConverterBool:
+ @classmethod
+ def read(cls, buf):
+ return cls.lift(buf.readU8())
+
+ @classmethod
+ def write(cls, value, buf):
+ buf.writeU8(cls.lower(value))
+
+ @staticmethod
+ def lift(value):
+ return int(value) != 0
+
+ @staticmethod
+ def lower(value):
+ return 1 if value else 0
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py
new file mode 100644
index 0000000000..e8a0ec56d9
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceRuntime.py
@@ -0,0 +1,73 @@
+import threading
+
+class ConcurrentHandleMap:
+ """
+ A map where inserting, getting and removing data is synchronized with a lock.
+ """
+
+ def __init__(self):
+ # type Handle = int
+ self._left_map = {} # type: Dict[Handle, Any]
+ self._right_map = {} # type: Dict[Any, Handle]
+
+ self._lock = threading.Lock()
+ self._current_handle = 0
+ self._stride = 1
+
+
+ def insert(self, obj):
+ with self._lock:
+ if obj in self._right_map:
+ return self._right_map[obj]
+ else:
+ handle = self._current_handle
+ self._current_handle += self._stride
+ self._left_map[handle] = obj
+ self._right_map[obj] = handle
+ return handle
+
+ def get(self, handle):
+ with self._lock:
+ return self._left_map.get(handle)
+
+ def remove(self, handle):
+ with self._lock:
+ if handle in self._left_map:
+ obj = self._left_map.pop(handle)
+ del self._right_map[obj]
+ return obj
+
+# Magic number for the Rust proxy to call using the same mechanism as every other method,
+# to free the callback once it's dropped by Rust.
+IDX_CALLBACK_FREE = 0
+
+class FfiConverterCallbackInterface:
+ _handle_map = ConcurrentHandleMap()
+
+ def __init__(self, cb):
+ self._foreign_callback = cb
+
+ def drop(self, handle):
+ self.__class__._handle_map.remove(handle)
+
+ @classmethod
+ def lift(cls, handle):
+ obj = cls._handle_map.get(handle)
+ if not obj:
+ raise InternalError("The object in the handle map has been dropped already")
+
+ return obj
+
+ @classmethod
+ def read(cls, buf):
+ handle = buf.readU64()
+ cls.lift(handle)
+
+ @classmethod
+ def lower(cls, cb):
+ handle = cls._handle_map.insert(cb)
+ return handle
+
+ @classmethod
+ def write(cls, cb, buf):
+ buf.writeU64(cls.lower(cb))
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py
new file mode 100644
index 0000000000..cf4411b8a0
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CallbackInterfaceTemplate.py
@@ -0,0 +1,104 @@
+{%- let cbi = ci.get_callback_interface_definition(id).unwrap() %}
+{%- let foreign_callback = format!("foreignCallback{}", canonical_type_name) %}
+
+{% if self.include_once_check("CallbackInterfaceRuntime.py") %}{% include "CallbackInterfaceRuntime.py" %}{% endif %}
+
+# Declaration and FfiConverters for {{ type_name }} Callback Interface
+
+class {{ type_name }}:
+ {% for meth in cbi.methods() -%}
+ def {{ meth.name()|fn_name }}({% call py::arg_list_decl(meth) %}):
+ raise NotImplementedError
+
+ {% endfor %}
+
+def py_{{ foreign_callback }}(handle, method, args, buf_ptr):
+ {% for meth in cbi.methods() -%}
+ {% let method_name = format!("invoke_{}", meth.name())|fn_name %}
+ def {{ method_name }}(python_callback, args):
+ {#- Unpacking args from the RustBuffer #}
+ {%- if meth.arguments().len() != 0 -%}
+ {#- Calling the concrete callback object #}
+ with args.consumeWithStream() as buf:
+ rval = python_callback.{{ meth.name()|fn_name }}(
+ {% for arg in meth.arguments() -%}
+ {{ arg|read_fn }}(buf)
+ {%- if !loop.last %}, {% endif %}
+ {% endfor -%}
+ )
+ {% else %}
+ rval = python_callback.{{ meth.name()|fn_name }}()
+ {% endif -%}
+
+ {#- Packing up the return value into a RustBuffer #}
+ {%- match meth.return_type() -%}
+ {%- when Some with (return_type) -%}
+ with RustBuffer.allocWithBuilder() as builder:
+ {{ return_type|write_fn }}(rval, builder)
+ return builder.finalize()
+ {%- else -%}
+ return RustBuffer.alloc(0)
+ {% endmatch -%}
+ {% endfor %}
+
+ cb = {{ ffi_converter_name }}.lift(handle)
+ if not cb:
+ raise InternalError("No callback in handlemap; this is a Uniffi bug")
+
+ if method == IDX_CALLBACK_FREE:
+ {{ ffi_converter_name }}.drop(handle)
+ # No return value.
+ # See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs`
+ return 0
+
+ {% for meth in cbi.methods() -%}
+ {% let method_name = format!("invoke_{}", meth.name())|fn_name -%}
+ if method == {{ loop.index }}:
+ # Call the method and handle any errors
+ # See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs` for details
+ try:
+ {%- match meth.throws_type() %}
+ {%- when Some(err) %}
+ try:
+ # Successful return
+ buf_ptr[0] = {{ method_name }}(cb, args)
+ return 1
+ except {{ err|type_name }} as e:
+ # Catch errors declared in the UDL file
+ with RustBuffer.allocWithBuilder() as builder:
+ {{ err|write_fn }}(e, builder)
+ buf_ptr[0] = builder.finalize()
+ return -2
+ {%- else %}
+ # Successful return
+ buf_ptr[0] = {{ method_name }}(cb, args)
+ return 1
+ {%- endmatch %}
+ except BaseException as e:
+ # Catch unexpected errors
+ try:
+ # Try to serialize the exception into a String
+ buf_ptr[0] = {{ Type::String.borrow()|lower_fn }}(repr(e))
+ except:
+ # If that fails, just give up
+ pass
+ return -1
+ {% endfor %}
+
+ # This should never happen, because an out of bounds method index won't
+ # ever be used. Once we can catch errors, we should return an InternalException.
+ # https://github.com/mozilla/uniffi-rs/issues/351
+
+ # An unexpected error happened.
+ # See docs of ForeignCallback in `uniffi/src/ffi/foreigncallbacks.rs`
+ return -1
+
+# We need to keep this function reference alive:
+# if they get GC'd while in use then UniFFI internals could attempt to call a function
+# that is in freed memory.
+# That would be...uh...bad. Yeah, that's the word. Bad.
+{{ foreign_callback }} = FOREIGN_CALLBACK_T(py_{{ foreign_callback }})
+
+# The FfiConverter which transforms the Callbacks in to Handles to pass to Rust.
+rust_call(lambda err: _UniFFILib.{{ cbi.ffi_init_callback().name() }}({{ foreign_callback }}, err))
+{{ ffi_converter_name }} = FfiConverterCallbackInterface({{ foreign_callback }})
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CustomType.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CustomType.py
new file mode 100644
index 0000000000..9ce6a21ced
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/CustomType.py
@@ -0,0 +1,52 @@
+{%- match python_config.custom_types.get(name.as_str()) %}
+{% when None %}
+{#- No custom type config, just forward all methods to our builtin type #}
+class FfiConverterType{{ name }}:
+ @staticmethod
+ def write(value, buf):
+ {{ builtin|ffi_converter_name }}.write(value, buf)
+
+ @staticmethod
+ def read(buf):
+ return {{ builtin|ffi_converter_name }}.read(buf)
+
+ @staticmethod
+ def lift(value):
+ return {{ builtin|ffi_converter_name }}.lift(value)
+
+ @staticmethod
+ def lower(value):
+ return {{ builtin|ffi_converter_name }}.lower(value)
+
+{%- when Some(config) %}
+
+{%- match config.imports %}
+{%- when Some(imports) %}
+{%- for import_name in imports %}
+{{ self.add_import(import_name) }}
+{%- endfor %}
+{%- else %}
+{%- endmatch %}
+
+{#- Custom type config supplied, use it to convert the builtin type #}
+class FfiConverterType{{ name }}:
+ @staticmethod
+ def write(value, buf):
+ builtin_value = {{ config.from_custom.render("value") }}
+ {{ builtin|write_fn }}(builtin_value, buf)
+
+ @staticmethod
+ def read(buf):
+ builtin_value = {{ builtin|read_fn }}(buf)
+ return {{ config.into_custom.render("builtin_value") }}
+
+ @staticmethod
+ def lift(value):
+ builtin_value = {{ builtin|lift_fn }}(value)
+ return {{ config.into_custom.render("builtin_value") }}
+
+ @staticmethod
+ def lower(value):
+ builtin_value = {{ config.from_custom.render("value") }}
+ return {{ builtin|lower_fn }}(builtin_value)
+{%- endmatch %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py
new file mode 100644
index 0000000000..16fdd986f3
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/DurationHelper.py
@@ -0,0 +1,19 @@
+# The Duration type.
+# There is a loss of precision when converting from Rust durations,
+# which are accurate to the nanosecond,
+# to Python durations, which are only accurate to the microsecond.
+class FfiConverterDuration(FfiConverterRustBuffer):
+ @staticmethod
+ def read(buf):
+ seconds = buf.readU64()
+ microseconds = buf.readU32() / 1.0e3
+ return datetime.timedelta(seconds=seconds, microseconds=microseconds)
+
+ @staticmethod
+ def write(value, buf):
+ seconds = value.seconds + value.days * 24 * 3600
+ nanoseconds = value.microseconds * 1000
+ if seconds < 0:
+ raise ValueError("Invalid duration, must be non-negative")
+ buf.writeI64(seconds)
+ buf.writeU32(nanoseconds)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py
new file mode 100644
index 0000000000..5c366d8855
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/EnumTemplate.py
@@ -0,0 +1,93 @@
+{#
+# Python has a built-in `enum` module which is nice to use, but doesn't support
+# variants with associated data. So, we switch here, and generate a stdlib `enum`
+# when none of the variants have associated data, or a generic nested-class
+# construct when they do.
+#}
+{%- let e = ci.get_enum_definition(name).unwrap() %}
+{% if e.is_flat() %}
+
+class {{ type_name }}(enum.Enum):
+ {% for variant in e.variants() -%}
+ {{ variant.name()|enum_variant_py }} = {{ loop.index }}
+ {% endfor %}
+{% else %}
+
+class {{ type_name }}:
+ def __init__(self):
+ raise RuntimeError("{{ type_name }} cannot be instantiated directly")
+
+ # Each enum variant is a nested class of the enum itself.
+ {% for variant in e.variants() -%}
+ class {{ variant.name()|enum_variant_py }}(object):
+ def __init__(self,{% for field in variant.fields() %}{{ field.name()|var_name }}{% if loop.last %}{% else %}, {% endif %}{% endfor %}):
+ {% if variant.has_fields() %}
+ {%- for field in variant.fields() %}
+ self.{{ field.name()|var_name }} = {{ field.name()|var_name }}
+ {%- endfor %}
+ {% else %}
+ pass
+ {% endif %}
+
+ def __str__(self):
+ return "{{ type_name }}.{{ variant.name()|enum_variant_py }}({% for field in variant.fields() %}{{ field.name()|var_name }}={}{% if loop.last %}{% else %}, {% endif %}{% endfor %})".format({% for field in variant.fields() %}self.{{ field.name()|var_name }}{% if loop.last %}{% else %}, {% endif %}{% endfor %})
+
+ def __eq__(self, other):
+ if not other.is_{{ variant.name()|var_name }}():
+ return False
+ {%- for field in variant.fields() %}
+ if self.{{ field.name()|var_name }} != other.{{ field.name()|var_name }}:
+ return False
+ {%- endfor %}
+ return True
+ {% endfor %}
+
+ # For each variant, we have an `is_NAME` method for easily checking
+ # whether an instance is that variant.
+ {% for variant in e.variants() -%}
+ def is_{{ variant.name()|var_name }}(self):
+ return isinstance(self, {{ type_name }}.{{ variant.name()|enum_variant_py }})
+ {% endfor %}
+
+# Now, a little trick - we make each nested variant class be a subclass of the main
+# enum class, so that method calls and instance checks etc will work intuitively.
+# We might be able to do this a little more neatly with a metaclass, but this'll do.
+{% for variant in e.variants() -%}
+{{ type_name }}.{{ variant.name()|enum_variant_py }} = type("{{ type_name }}.{{ variant.name()|enum_variant_py }}", ({{ type_name }}.{{variant.name()|enum_variant_py}}, {{ type_name }},), {})
+{% endfor %}
+
+{% endif %}
+
+class {{ ffi_converter_name }}(FfiConverterRustBuffer):
+ @staticmethod
+ def read(buf):
+ variant = buf.readI32()
+
+ {%- for variant in e.variants() %}
+ if variant == {{ loop.index }}:
+ {%- if e.is_flat() %}
+ return {{ type_name }}.{{variant.name()|enum_variant_py}}
+ {%- else %}
+ return {{ type_name }}.{{variant.name()|enum_variant_py}}(
+ {%- for field in variant.fields() %}
+ {{ field|read_fn }}(buf),
+ {%- endfor %}
+ )
+ {%- endif %}
+ {%- endfor %}
+ raise InternalError("Raw enum value doesn't match any cases")
+
+ def write(value, buf):
+ {%- for variant in e.variants() %}
+ {%- if e.is_flat() %}
+ if value == {{ type_name }}.{{ variant.name()|enum_variant_py }}:
+ buf.writeI32({{ loop.index }})
+ {%- else %}
+ if value.is_{{ variant.name()|var_name }}():
+ buf.writeI32({{ loop.index }})
+ {%- for field in variant.fields() %}
+ {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf)
+ {%- endfor %}
+ {%- endif %}
+ {%- endfor %}
+
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py
new file mode 100644
index 0000000000..305ae88618
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ErrorTemplate.py
@@ -0,0 +1,75 @@
+{%- let e = ci.get_error_definition(name).unwrap() %}
+
+# {{ type_name }}
+# We want to define each variant as a nested class that's also a subclass,
+# which is tricky in Python. To accomplish this we're going to create each
+# class separated, then manually add the child classes to the base class's
+# __dict__. All of this happens in dummy class to avoid polluting the module
+# namespace.
+class UniFFIExceptionTmpNamespace:
+ class {{ type_name }}(Exception):
+ pass
+ {% for variant in e.variants() %}
+ {%- let variant_type_name = variant.name()|class_name %}
+
+ {%- if e.is_flat() %}
+ class {{ variant_type_name }}({{ type_name }}):
+ def __str__(self):
+ return "{{ type_name }}.{{ variant_type_name }}({})".format(repr(super().__str__()))
+ {%- else %}
+ class {{ variant_type_name }}({{ type_name }}):
+ def __init__(self{% for field in variant.fields() %}, {{ field.name()|var_name }}{% endfor %}):
+ {%- if variant.has_fields() %}
+ {%- for field in variant.fields() %}
+ self.{{ field.name()|var_name }} = {{ field.name()|var_name }}
+ {%- endfor %}
+ {%- else %}
+ pass
+ {%- endif %}
+
+ def __str__(self):
+ {%- if variant.has_fields() %}
+ field_parts = [
+ {%- for field in variant.fields() %}
+ '{{ field.name()|var_name }}={!r}'.format(self.{{ field.name()|var_name }}),
+ {%- endfor %}
+ ]
+ return "{{ type_name }}.{{ variant_type_name }}({})".format(', '.join(field_parts))
+ {%- else %}
+ return "{{ type_name }}.{{ variant_type_name }}()"
+ {%- endif %}
+ {%- endif %}
+
+ {{ type_name }}.{{ variant_type_name }} = {{ variant_type_name }}
+ {%- endfor %}
+{{ type_name }} = UniFFIExceptionTmpNamespace.{{ type_name }}
+del UniFFIExceptionTmpNamespace
+
+
+class {{ ffi_converter_name }}(FfiConverterRustBuffer):
+ @staticmethod
+ def read(buf):
+ variant = buf.readI32()
+ {%- for variant in e.variants() %}
+ if variant == {{ loop.index }}:
+ return {{ type_name }}.{{ variant.name()|class_name }}(
+ {%- if e.is_flat() %}
+ {{ Type::String.borrow()|read_fn }}(buf),
+ {%- else %}
+ {%- for field in variant.fields() %}
+ {{ field.name()|var_name }}={{ field|read_fn }}(buf),
+ {%- endfor %}
+ {%- endif %}
+ )
+ {%- endfor %}
+ raise InternalError("Raw enum value doesn't match any cases")
+
+ @staticmethod
+ def write(value, buf):
+ {%- for variant in e.variants() %}
+ if isinstance(value, {{ type_name }}.{{ variant.name()|class_name }}):
+ buf.writeI32({{ loop.index }})
+ {%- for field in variant.fields() %}
+ {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf)
+ {%- endfor %}
+ {%- endfor %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py
new file mode 100644
index 0000000000..a5f2584c62
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ExternalTemplate.py
@@ -0,0 +1,7 @@
+{%- let mod_name = crate_name|fn_name %}
+
+{%- let ffi_converter_name = "FfiConverterType{}"|format(name) %}
+{{ self.add_import_of(mod_name, ffi_converter_name) }}
+
+{%- let rustbuffer_local_name = "RustBuffer{}"|format(name) %}
+{{ self.add_import_of_as(mod_name, "RustBuffer", rustbuffer_local_name) }}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py
new file mode 100644
index 0000000000..e5783dd158
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float32Helper.py
@@ -0,0 +1,8 @@
+class FfiConverterFloat(FfiConverterPrimitive):
+ @staticmethod
+ def read(buf):
+ return buf.readFloat()
+
+ @staticmethod
+ def write(value, buf):
+ buf.writeFloat(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py
new file mode 100644
index 0000000000..57c2131eb8
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Float64Helper.py
@@ -0,0 +1,8 @@
+class FfiConverterDouble(FfiConverterPrimitive):
+ @staticmethod
+ def read(buf):
+ return buf.readDouble()
+
+ @staticmethod
+ def write(value, buf):
+ buf.writeDouble(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Helpers.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Helpers.py
new file mode 100644
index 0000000000..fbce501981
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Helpers.py
@@ -0,0 +1,67 @@
+# A handful of classes and functions to support the generated data structures.
+# This would be a good candidate for isolating in its own ffi-support lib.
+
+class InternalError(Exception):
+ pass
+
+class RustCallStatus(ctypes.Structure):
+ """
+ Error runtime.
+ """
+ _fields_ = [
+ ("code", ctypes.c_int8),
+ ("error_buf", RustBuffer),
+ ]
+
+ # These match the values from the uniffi::rustcalls module
+ CALL_SUCCESS = 0
+ CALL_ERROR = 1
+ CALL_PANIC = 2
+
+ def __str__(self):
+ if self.code == RustCallStatus.CALL_SUCCESS:
+ return "RustCallStatus(CALL_SUCCESS)"
+ elif self.code == RustCallStatus.CALL_ERROR:
+ return "RustCallStatus(CALL_ERROR)"
+ elif self.code == RustCallStatus.CALL_PANIC:
+ return "RustCallStatus(CALL_PANIC)"
+ else:
+ return "RustCallStatus(<invalid code>)"
+
+def rust_call(fn, *args):
+ # Call a rust function
+ return rust_call_with_error(None, fn, *args)
+
+def rust_call_with_error(error_ffi_converter, fn, *args):
+ # Call a rust function and handle any errors
+ #
+ # This function is used for rust calls that return Result<> and therefore can set the CALL_ERROR status code.
+ # error_ffi_converter must be set to the FfiConverter for the error class that corresponds to the result.
+ call_status = RustCallStatus(code=RustCallStatus.CALL_SUCCESS, error_buf=RustBuffer(0, 0, None))
+
+ args_with_error = args + (ctypes.byref(call_status),)
+ result = fn(*args_with_error)
+ if call_status.code == RustCallStatus.CALL_SUCCESS:
+ return result
+ elif call_status.code == RustCallStatus.CALL_ERROR:
+ if error_ffi_converter is None:
+ call_status.error_buf.free()
+ raise InternalError("rust_call_with_error: CALL_ERROR, but error_ffi_converter is None")
+ else:
+ raise error_ffi_converter.lift(call_status.error_buf)
+ elif call_status.code == RustCallStatus.CALL_PANIC:
+ # When the rust code sees a panic, it tries to construct a RustBuffer
+ # with the message. But if that code panics, then it just sends back
+ # an empty buffer.
+ if call_status.error_buf.len > 0:
+ msg = FfiConverterString.lift(call_status.error_buf)
+ else:
+ msg = "Unknown rust panic"
+ raise InternalError(msg)
+ else:
+ raise InternalError("Invalid RustCallStatus code: {}".format(
+ call_status.code))
+
+# A function pointer for a callback as defined by UniFFI.
+# Rust definition `fn(handle: u64, method: u32, args: RustBuffer, buf_ptr: *mut RustBuffer) -> int`
+FOREIGN_CALLBACK_T = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_ulonglong, ctypes.c_ulong, RustBuffer, ctypes.POINTER(RustBuffer))
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py
new file mode 100644
index 0000000000..b3d9602746
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int16Helper.py
@@ -0,0 +1,8 @@
+class FfiConverterInt16(FfiConverterPrimitive):
+ @staticmethod
+ def read(buf):
+ return buf.readI16()
+
+ @staticmethod
+ def write(value, buf):
+ buf.writeI16(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py
new file mode 100644
index 0000000000..47a54d8c4a
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int32Helper.py
@@ -0,0 +1,8 @@
+class FfiConverterInt32(FfiConverterPrimitive):
+ @staticmethod
+ def read(buf):
+ return buf.readI32()
+
+ @staticmethod
+ def write(value, buf):
+ buf.writeI32(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py
new file mode 100644
index 0000000000..471b9967ce
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int64Helper.py
@@ -0,0 +1,8 @@
+class FfiConverterInt64(FfiConverterPrimitive):
+ @staticmethod
+ def read(buf):
+ return buf.readI64()
+
+ @staticmethod
+ def write(value, buf):
+ buf.writeI64(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py
new file mode 100644
index 0000000000..524fea7ea9
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Int8Helper.py
@@ -0,0 +1,8 @@
+class FfiConverterInt8(FfiConverterPrimitive):
+ @staticmethod
+ def read(buf):
+ return buf.readI8()
+
+ @staticmethod
+ def write(value, buf):
+ buf.writeI8(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py
new file mode 100644
index 0000000000..7f42daa7a9
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/MapTemplate.py
@@ -0,0 +1,27 @@
+{%- let key_ffi_converter = key_type|ffi_converter_name %}
+{%- let value_ffi_converter = value_type|ffi_converter_name %}
+
+class {{ ffi_converter_name }}(FfiConverterRustBuffer):
+ @classmethod
+ def write(cls, items, buf):
+ buf.writeI32(len(items))
+ for (key, value) in items.items():
+ {{ key_ffi_converter }}.write(key, buf)
+ {{ value_ffi_converter }}.write(value, buf)
+
+ @classmethod
+ def read(cls, buf):
+ count = buf.readI32()
+ if count < 0:
+ raise InternalError("Unexpected negative map size")
+
+ # It would be nice to use a dict comprehension,
+ # but in Python 3.7 and before the evaluation order is not according to spec,
+ # so we we're reading the value before the key.
+ # This loop makes the order explicit: first reading the key, then the value.
+ d = {}
+ for i in range(count):
+ key = {{ key_ffi_converter }}.read(buf)
+ val = {{ value_ffi_converter }}.read(buf)
+ d[key] = val
+ return d
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py
new file mode 100644
index 0000000000..4867ecb9b4
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/NamespaceLibraryTemplate.py
@@ -0,0 +1,39 @@
+# This is how we find and load the dynamic library provided by the component.
+# For now we just look it up by name.
+#
+# XXX TODO: This will probably grow some magic for resolving megazording in future.
+# E.g. we might start by looking for the named component in `libuniffi.so` and if
+# that fails, fall back to loading it separately from `lib${componentName}.so`.
+
+from pathlib import Path
+
+def loadIndirect():
+ if sys.platform == "darwin":
+ libname = "lib{}.dylib"
+ elif sys.platform.startswith("win"):
+ # As of python3.8, ctypes does not seem to search $PATH when loading DLLs.
+ # We could use `os.add_dll_directory` to configure the search path, but
+ # it doesn't feel right to mess with application-wide settings. Let's
+ # assume that the `.dll` is next to the `.py` file and load by full path.
+ libname = os.path.join(
+ os.path.dirname(__file__),
+ "{}.dll",
+ )
+ else:
+ # Anything else must be an ELF platform - Linux, *BSD, Solaris/illumos
+ libname = "lib{}.so"
+
+ lib = libname.format("{{ config.cdylib_name() }}")
+ path = str(Path(__file__).parent / lib)
+ return ctypes.cdll.LoadLibrary(path)
+
+# A ctypes library to expose the extern-C FFI definitions.
+# This is an implementation detail which will be called internally by the public API.
+
+_UniFFILib = loadIndirect()
+{%- for func in ci.iter_ffi_function_definitions() %}
+_UniFFILib.{{ func.name() }}.argtypes = (
+ {%- call py::arg_list_ffi_decl(func) -%}
+)
+_UniFFILib.{{ func.name() }}.restype = {% match func.return_type() %}{% when Some with (type_) %}{{ type_|ffi_type_name }}{% when None %}None{% endmatch %}
+{%- endfor %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py
new file mode 100644
index 0000000000..cc4cc35b4e
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/ObjectTemplate.py
@@ -0,0 +1,74 @@
+{%- let obj = ci.get_object_definition(name).unwrap() %}
+
+class {{ type_name }}(object):
+ {%- match obj.primary_constructor() %}
+ {%- when Some with (cons) %}
+ def __init__(self, {% call py::arg_list_decl(cons) -%}):
+ {%- call py::setup_args_extra_indent(cons) %}
+ self._pointer = {% call py::to_ffi_call(cons) %}
+ {%- when None %}
+ {%- endmatch %}
+
+ def __del__(self):
+ # In case of partial initialization of instances.
+ pointer = getattr(self, "_pointer", None)
+ if pointer is not None:
+ rust_call(_UniFFILib.{{ obj.ffi_object_free().name() }}, pointer)
+
+ # Used by alternative constructors or any methods which return this type.
+ @classmethod
+ def _make_instance_(cls, pointer):
+ # Lightly yucky way to bypass the usual __init__ logic
+ # and just create a new instance with the required pointer.
+ inst = cls.__new__(cls)
+ inst._pointer = pointer
+ return inst
+
+ {% for cons in obj.alternate_constructors() -%}
+ @classmethod
+ def {{ cons.name()|fn_name }}(cls, {% call py::arg_list_decl(cons) %}):
+ {%- call py::setup_args_extra_indent(cons) %}
+ # Call the (fallible) function before creating any half-baked object instances.
+ pointer = {% call py::to_ffi_call(cons) %}
+ return cls._make_instance_(pointer)
+ {% endfor %}
+
+ {% for meth in obj.methods() -%}
+ {%- match meth.return_type() -%}
+
+ {%- when Some with (return_type) -%}
+ def {{ meth.name()|fn_name }}(self, {% call py::arg_list_decl(meth) %}):
+ {%- call py::setup_args_extra_indent(meth) %}
+ return {{ return_type|lift_fn }}(
+ {% call py::to_ffi_call_with_prefix("self._pointer", meth) %}
+ )
+
+ {%- when None -%}
+ def {{ meth.name()|fn_name }}(self, {% call py::arg_list_decl(meth) %}):
+ {%- call py::setup_args_extra_indent(meth) %}
+ {% call py::to_ffi_call_with_prefix("self._pointer", meth) %}
+ {% endmatch %}
+ {% endfor %}
+
+
+class {{ ffi_converter_name }}:
+ @classmethod
+ def read(cls, buf):
+ ptr = buf.readU64()
+ if ptr == 0:
+ raise InternalError("Raw pointer value was null")
+ return cls.lift(ptr)
+
+ @classmethod
+ def write(cls, value, buf):
+ if not isinstance(value, {{ type_name }}):
+ raise TypeError("Expected {{ type_name }} instance, {} found".format(value.__class__.__name__))
+ buf.writeU64(cls.lower(value))
+
+ @staticmethod
+ def lift(value):
+ return {{ type_name }}._make_instance_(value)
+
+ @staticmethod
+ def lower(value):
+ return value._pointer
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py
new file mode 100644
index 0000000000..70f705362f
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/OptionalTemplate.py
@@ -0,0 +1,21 @@
+{%- let inner_ffi_converter = inner_type|ffi_converter_name %}
+
+class {{ ffi_converter_name }}(FfiConverterRustBuffer):
+ @classmethod
+ def write(cls, value, buf):
+ if value is None:
+ buf.writeU8(0)
+ return
+
+ buf.writeU8(1)
+ {{ inner_ffi_converter }}.write(value, buf)
+
+ @classmethod
+ def read(cls, buf):
+ flag = buf.readU8()
+ if flag == 0:
+ return None
+ elif flag == 1:
+ return {{ inner_ffi_converter }}.read(buf)
+ else:
+ raise InternalError("Unexpected flag byte for optional type")
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py
new file mode 100644
index 0000000000..2009d59e8a
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RecordTemplate.py
@@ -0,0 +1,45 @@
+{%- let rec = ci.get_record_definition(name).unwrap() %}
+class {{ type_name }}:
+
+ def __init__(self, {% for field in rec.fields() %}
+ {{- field.name()|var_name }}
+ {%- if field.default_value().is_some() %} = DEFAULT{% endif %}
+ {%- if !loop.last %}, {% endif %}
+ {%- endfor %}):
+ {%- for field in rec.fields() %}
+ {%- let field_name = field.name()|var_name %}
+ {%- match field.default_value() %}
+ {%- when None %}
+ self.{{ field_name }} = {{ field_name }}
+ {%- when Some with(literal) %}
+ if {{ field_name }} is DEFAULT:
+ self.{{ field_name }} = {{ literal|literal_py(field) }}
+ else:
+ self.{{ field_name }} = {{ field_name }}
+ {%- endmatch %}
+ {%- endfor %}
+
+ def __str__(self):
+ return "{{ type_name }}({% for field in rec.fields() %}{{ field.name()|var_name }}={}{% if loop.last %}{% else %}, {% endif %}{% endfor %})".format({% for field in rec.fields() %}self.{{ field.name()|var_name }}{% if loop.last %}{% else %}, {% endif %}{% endfor %})
+
+ def __eq__(self, other):
+ {%- for field in rec.fields() %}
+ if self.{{ field.name()|var_name }} != other.{{ field.name()|var_name }}:
+ return False
+ {%- endfor %}
+ return True
+
+class {{ ffi_converter_name }}(FfiConverterRustBuffer):
+ @staticmethod
+ def read(buf):
+ return {{ type_name }}(
+ {%- for field in rec.fields() %}
+ {{ field.name()|var_name }}={{ field|read_fn }}(buf),
+ {%- endfor %}
+ )
+
+ @staticmethod
+ def write(value, buf):
+ {%- for field in rec.fields() %}
+ {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf)
+ {%- endfor %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py
new file mode 100644
index 0000000000..eafd2346bb
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferHelper.py
@@ -0,0 +1,23 @@
+# Types conforming to `FfiConverterPrimitive` pass themselves directly over the FFI.
+class FfiConverterPrimitive:
+ @classmethod
+ def lift(cls, value):
+ return value
+
+ @classmethod
+ def lower(cls, value):
+ return value
+
+# Helper class for wrapper types that will always go through a RustBuffer.
+# Classes should inherit from this and implement the `read` and `write` static methods.
+class FfiConverterRustBuffer:
+ @classmethod
+ def lift(cls, rbuf):
+ with rbuf.consumeWithStream() as stream:
+ return cls.read(stream)
+
+ @classmethod
+ def lower(cls, value):
+ with RustBuffer.allocWithBuilder() as builder:
+ cls.write(value, builder)
+ return builder.finalize()
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py
new file mode 100644
index 0000000000..95a3b1f706
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/RustBufferTemplate.py
@@ -0,0 +1,190 @@
+
+class RustBuffer(ctypes.Structure):
+ _fields_ = [
+ ("capacity", ctypes.c_int32),
+ ("len", ctypes.c_int32),
+ ("data", ctypes.POINTER(ctypes.c_char)),
+ ]
+
+ @staticmethod
+ def alloc(size):
+ return rust_call(_UniFFILib.{{ ci.ffi_rustbuffer_alloc().name() }}, size)
+
+ @staticmethod
+ def reserve(rbuf, additional):
+ return rust_call(_UniFFILib.{{ ci.ffi_rustbuffer_reserve().name() }}, rbuf, additional)
+
+ def free(self):
+ return rust_call(_UniFFILib.{{ ci.ffi_rustbuffer_free().name() }}, self)
+
+ def __str__(self):
+ return "RustBuffer(capacity={}, len={}, data={})".format(
+ self.capacity,
+ self.len,
+ self.data[0:self.len]
+ )
+
+ @contextlib.contextmanager
+ def allocWithBuilder():
+ """Context-manger to allocate a buffer using a RustBufferBuilder.
+
+ The allocated buffer will be automatically freed if an error occurs, ensuring that
+ we don't accidentally leak it.
+ """
+ builder = RustBufferBuilder()
+ try:
+ yield builder
+ except:
+ builder.discard()
+ raise
+
+ @contextlib.contextmanager
+ def consumeWithStream(self):
+ """Context-manager to consume a buffer using a RustBufferStream.
+
+ The RustBuffer will be freed once the context-manager exits, ensuring that we don't
+ leak it even if an error occurs.
+ """
+ try:
+ s = RustBufferStream(self)
+ yield s
+ if s.remaining() != 0:
+ raise RuntimeError("junk data left in buffer after consuming")
+ finally:
+ self.free()
+
+
+class ForeignBytes(ctypes.Structure):
+ _fields_ = [
+ ("len", ctypes.c_int32),
+ ("data", ctypes.POINTER(ctypes.c_char)),
+ ]
+
+ def __str__(self):
+ return "ForeignBytes(len={}, data={})".format(self.len, self.data[0:self.len])
+
+
+class RustBufferStream(object):
+ """
+ Helper for structured reading of bytes from a RustBuffer
+ """
+
+ def __init__(self, rbuf):
+ self.rbuf = rbuf
+ self.offset = 0
+
+ def remaining(self):
+ return self.rbuf.len - self.offset
+
+ def _unpack_from(self, size, format):
+ if self.offset + size > self.rbuf.len:
+ raise InternalError("read past end of rust buffer")
+ value = struct.unpack(format, self.rbuf.data[self.offset:self.offset+size])[0]
+ self.offset += size
+ return value
+
+ def read(self, size):
+ if self.offset + size > self.rbuf.len:
+ raise InternalError("read past end of rust buffer")
+ data = self.rbuf.data[self.offset:self.offset+size]
+ self.offset += size
+ return data
+
+ def readI8(self):
+ return self._unpack_from(1, ">b")
+
+ def readU8(self):
+ return self._unpack_from(1, ">B")
+
+ def readI16(self):
+ return self._unpack_from(2, ">h")
+
+ def readU16(self):
+ return self._unpack_from(2, ">H")
+
+ def readI32(self):
+ return self._unpack_from(4, ">i")
+
+ def readU32(self):
+ return self._unpack_from(4, ">I")
+
+ def readI64(self):
+ return self._unpack_from(8, ">q")
+
+ def readU64(self):
+ return self._unpack_from(8, ">Q")
+
+ def readFloat(self):
+ v = self._unpack_from(4, ">f")
+ return v
+
+ def readDouble(self):
+ return self._unpack_from(8, ">d")
+
+
+class RustBufferBuilder(object):
+ """
+ Helper for structured writing of bytes into a RustBuffer.
+ """
+
+ def __init__(self):
+ self.rbuf = RustBuffer.alloc(16)
+ self.rbuf.len = 0
+
+ def finalize(self):
+ rbuf = self.rbuf
+ self.rbuf = None
+ return rbuf
+
+ def discard(self):
+ if self.rbuf is not None:
+ rbuf = self.finalize()
+ rbuf.free()
+
+ @contextlib.contextmanager
+ def _reserve(self, numBytes):
+ if self.rbuf.len + numBytes > self.rbuf.capacity:
+ self.rbuf = RustBuffer.reserve(self.rbuf, numBytes)
+ yield None
+ self.rbuf.len += numBytes
+
+ def _pack_into(self, size, format, value):
+ with self._reserve(size):
+ # XXX TODO: I feel like I should be able to use `struct.pack_into` here but can't figure it out.
+ for i, byte in enumerate(struct.pack(format, value)):
+ self.rbuf.data[self.rbuf.len + i] = byte
+
+ def write(self, value):
+ with self._reserve(len(value)):
+ for i, byte in enumerate(value):
+ self.rbuf.data[self.rbuf.len + i] = byte
+
+ def writeI8(self, v):
+ self._pack_into(1, ">b", v)
+
+ def writeU8(self, v):
+ self._pack_into(1, ">B", v)
+
+ def writeI16(self, v):
+ self._pack_into(2, ">h", v)
+
+ def writeU16(self, v):
+ self._pack_into(2, ">H", v)
+
+ def writeI32(self, v):
+ self._pack_into(4, ">i", v)
+
+ def writeU32(self, v):
+ self._pack_into(4, ">I", v)
+
+ def writeI64(self, v):
+ self._pack_into(8, ">q", v)
+
+ def writeU64(self, v):
+ self._pack_into(8, ">Q", v)
+
+ def writeFloat(self, v):
+ self._pack_into(4, ">f", v)
+
+ def writeDouble(self, v):
+ self._pack_into(8, ">d", v)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py
new file mode 100644
index 0000000000..6de7bf2d4d
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/SequenceTemplate.py
@@ -0,0 +1,19 @@
+{%- let inner_ffi_converter = inner_type|ffi_converter_name %}
+
+class {{ ffi_converter_name}}(FfiConverterRustBuffer):
+ @classmethod
+ def write(cls, value, buf):
+ items = len(value)
+ buf.writeI32(items)
+ for item in value:
+ {{ inner_ffi_converter }}.write(item, buf)
+
+ @classmethod
+ def read(cls, buf):
+ count = buf.readI32()
+ if count < 0:
+ raise InternalError("Unexpected negative sequence length")
+
+ return [
+ {{ inner_ffi_converter }}.read(buf) for i in range(count)
+ ]
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/StringHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/StringHelper.py
new file mode 100644
index 0000000000..f205f7c746
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/StringHelper.py
@@ -0,0 +1,25 @@
+class FfiConverterString:
+ @staticmethod
+ def read(buf):
+ size = buf.readI32()
+ if size < 0:
+ raise InternalError("Unexpected negative string length")
+ utf8Bytes = buf.read(size)
+ return utf8Bytes.decode("utf-8")
+
+ @staticmethod
+ def write(value, buf):
+ utf8Bytes = value.encode("utf-8")
+ buf.writeI32(len(utf8Bytes))
+ buf.write(utf8Bytes)
+
+ @staticmethod
+ def lift(buf):
+ with buf.consumeWithStream() as stream:
+ return stream.read(stream.remaining()).decode("utf-8")
+
+ @staticmethod
+ def lower(value):
+ with RustBuffer.allocWithBuilder() as builder:
+ builder.write(value.encode("utf-8"))
+ return builder.finalize()
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py
new file mode 100644
index 0000000000..ac87697551
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TimestampHelper.py
@@ -0,0 +1,30 @@
+# The Timestamp type.
+# There is a loss of precision when converting from Rust timestamps,
+# which are accurate to the nanosecond,
+# to Python datetimes, which have a variable precision due to the use of float as representation.
+class FfiConverterTimestamp(FfiConverterRustBuffer):
+ @staticmethod
+ def read(buf):
+ seconds = buf.readI64()
+ microseconds = buf.readU32() / 1000
+ # Use fromtimestamp(0) then add the seconds using a timedelta. This
+ # ensures that we get OverflowError rather than ValueError when
+ # seconds is too large.
+ if seconds >= 0:
+ return datetime.datetime.fromtimestamp(0, tz=datetime.timezone.utc) + datetime.timedelta(seconds=seconds, microseconds=microseconds)
+ else:
+ return datetime.datetime.fromtimestamp(0, tz=datetime.timezone.utc) - datetime.timedelta(seconds=-seconds, microseconds=microseconds)
+
+ @staticmethod
+ def write(value, buf):
+ if value >= datetime.datetime.fromtimestamp(0, datetime.timezone.utc):
+ sign = 1
+ delta = value - datetime.datetime.fromtimestamp(0, datetime.timezone.utc)
+ else:
+ sign = -1
+ delta = datetime.datetime.fromtimestamp(0, datetime.timezone.utc) - value
+
+ seconds = delta.seconds + delta.days * 24 * 3600
+ nanoseconds = delta.microseconds * 1000
+ buf.writeI64(sign * seconds)
+ buf.writeU32(nanoseconds)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py
new file mode 100644
index 0000000000..2d0e09f22d
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/TopLevelFunctionTemplate.py
@@ -0,0 +1,13 @@
+{%- match func.return_type() -%}
+{%- when Some with (return_type) %}
+
+def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}):
+ {%- call py::setup_args(func) %}
+ return {{ return_type|lift_fn }}({% call py::to_ffi_call(func) %})
+
+{% when None %}
+
+def {{ func.name()|fn_name }}({%- call py::arg_list_decl(func) -%}):
+ {%- call py::setup_args(func) %}
+ {% call py::to_ffi_call(func) %}
+{% endmatch %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Types.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Types.py
new file mode 100644
index 0000000000..ce65472a64
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/Types.py
@@ -0,0 +1,93 @@
+{%- import "macros.py" as py %}
+
+{%- for type_ in ci.iter_types() %}
+{%- let type_name = type_|type_name %}
+{%- let ffi_converter_name = type_|ffi_converter_name %}
+{%- let canonical_type_name = type_|canonical_name %}
+
+{#
+ # Map `Type` instances to an include statement for that type.
+ #
+ # There is a companion match in `PythonCodeOracle::create_code_type()` which performs a similar function for the
+ # Rust code.
+ #
+ # - When adding additional types here, make sure to also add a match arm to that function.
+ # - To keep things manageable, let's try to limit ourselves to these 2 mega-matches
+ #}
+{%- match type_ %}
+
+{%- when Type::Boolean %}
+{%- include "BooleanHelper.py" %}
+
+{%- when Type::Int8 %}
+{%- include "Int8Helper.py" %}
+
+{%- when Type::Int16 %}
+{%- include "Int16Helper.py" %}
+
+{%- when Type::Int32 %}
+{%- include "Int32Helper.py" %}
+
+{%- when Type::Int64 %}
+{%- include "Int64Helper.py" %}
+
+{%- when Type::UInt8 %}
+{%- include "UInt8Helper.py" %}
+
+{%- when Type::UInt16 %}
+{%- include "UInt16Helper.py" %}
+
+{%- when Type::UInt32 %}
+{%- include "UInt32Helper.py" %}
+
+{%- when Type::UInt64 %}
+{%- include "UInt64Helper.py" %}
+
+{%- when Type::Float32 %}
+{%- include "Float32Helper.py" %}
+
+{%- when Type::Float64 %}
+{%- include "Float64Helper.py" %}
+
+{%- when Type::String %}
+{%- include "StringHelper.py" %}
+
+{%- when Type::Enum(name) %}
+{%- include "EnumTemplate.py" %}
+
+{%- when Type::Error(name) %}
+{%- include "ErrorTemplate.py" %}
+
+{%- when Type::Record(name) %}
+{%- include "RecordTemplate.py" %}
+
+{%- when Type::Object(name) %}
+{%- include "ObjectTemplate.py" %}
+
+{%- when Type::Timestamp %}
+{%- include "TimestampHelper.py" %}
+
+{%- when Type::Duration %}
+{%- include "DurationHelper.py" %}
+
+{%- when Type::Optional(inner_type) %}
+{%- include "OptionalTemplate.py" %}
+
+{%- when Type::Sequence(inner_type) %}
+{%- include "SequenceTemplate.py" %}
+
+{%- when Type::Map(key_type, value_type) %}
+{%- include "MapTemplate.py" %}
+
+{%- when Type::CallbackInterface(id) %}
+{%- include "CallbackInterfaceTemplate.py" %}
+
+{%- when Type::Custom { name, builtin } %}
+{%- include "CustomType.py" %}
+
+{%- when Type::External { name, crate_name } %}
+{%- include "ExternalTemplate.py" %}
+
+{%- else %}
+{%- endmatch %}
+{%- endfor %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py
new file mode 100644
index 0000000000..f1cd114c0c
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt16Helper.py
@@ -0,0 +1,8 @@
+class FfiConverterUInt16(FfiConverterPrimitive):
+ @staticmethod
+ def read(buf):
+ return buf.readU16()
+
+ @staticmethod
+ def write(value, buf):
+ buf.writeU16(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py
new file mode 100644
index 0000000000..290cd5fe82
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt32Helper.py
@@ -0,0 +1,8 @@
+class FfiConverterUInt32(FfiConverterPrimitive):
+ @staticmethod
+ def read(buf):
+ return buf.readU32()
+
+ @staticmethod
+ def write(value, buf):
+ buf.writeU32(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py
new file mode 100644
index 0000000000..f57dec1d92
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt64Helper.py
@@ -0,0 +1,8 @@
+class FfiConverterUInt64(FfiConverterPrimitive):
+ @staticmethod
+ def read(buf):
+ return buf.readU64()
+
+ @staticmethod
+ def write(value, buf):
+ buf.writeU64(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py
new file mode 100644
index 0000000000..3a1582351b
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/UInt8Helper.py
@@ -0,0 +1,8 @@
+class FfiConverterUInt8(FfiConverterPrimitive):
+ @staticmethod
+ def read(buf):
+ return buf.readU8()
+
+ @staticmethod
+ def write(value, buf):
+ buf.writeU8(value)
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/macros.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/macros.py
new file mode 100644
index 0000000000..d11fcae921
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/macros.py
@@ -0,0 +1,101 @@
+{#
+// Template to call into rust. Used in several places.
+// Variable names in `arg_list_decl` should match up with arg lists
+// passed to rust via `_arg_list_ffi_call`
+#}
+
+{%- macro to_ffi_call(func) -%}
+ {%- match func.throws_type() -%}
+ {%- when Some with (e) -%}
+rust_call_with_error({{ e|ffi_converter_name }},
+ {%- else -%}
+rust_call(
+ {%- endmatch -%}
+ _UniFFILib.{{ func.ffi_func().name() }},
+ {%- call _arg_list_ffi_call(func) -%}
+)
+{%- endmacro -%}
+
+{%- macro to_ffi_call_with_prefix(prefix, func) -%}
+ {%- match func.throws_type() -%}
+ {%- when Some with (e) -%}
+rust_call_with_error(
+ {{ e|ffi_converter_name }},
+ {%- else -%}
+rust_call(
+ {%- endmatch -%}
+ _UniFFILib.{{ func.ffi_func().name() }},
+ {{- prefix }},
+ {%- call _arg_list_ffi_call(func) -%}
+)
+{%- endmacro -%}
+
+{%- macro _arg_list_ffi_call(func) %}
+ {%- for arg in func.arguments() %}
+ {{ arg|lower_fn }}({{ arg.name()|var_name }})
+ {%- if !loop.last %},{% endif %}
+ {%- endfor %}
+{%- endmacro -%}
+
+{#-
+// Arglist as used in Python declarations of methods, functions and constructors.
+// Note the var_name and type_name filters.
+-#}
+
+{% macro arg_list_decl(func) %}
+ {%- for arg in func.arguments() -%}
+ {{ arg.name()|var_name }}
+ {%- match arg.default_value() %}
+ {%- when Some with(literal) %} = DEFAULT
+ {%- else %}
+ {%- endmatch %}
+ {%- if !loop.last %},{% endif -%}
+ {%- endfor %}
+{%- endmacro %}
+
+{#-
+// Arglist as used in the _UniFFILib function declarations.
+// Note unfiltered name but ffi_type_name filters.
+-#}
+{%- macro arg_list_ffi_decl(func) %}
+ {%- for arg in func.arguments() %}
+ {{ arg.type_().borrow()|ffi_type_name }},
+ {%- endfor %}
+ ctypes.POINTER(RustCallStatus),
+{% endmacro -%}
+
+{#
+ # Setup function arguments by initializing default values and passing other
+ # values through coerce.
+ #}
+{%- macro setup_args(func) %}
+ {%- for arg in func.arguments() %}
+ {%- match arg.default_value() %}
+ {%- when None %}
+ {{ arg.name()|var_name }} = {{ arg.name()|var_name|coerce_py(arg.type_().borrow()) -}}
+ {%- when Some with(literal) %}
+ if {{ arg.name()|var_name }} is DEFAULT:
+ {{ arg.name()|var_name }} = {{ literal|literal_py(arg.type_().borrow()) }}
+ else:
+ {{ arg.name()|var_name }} = {{ arg.name()|var_name|coerce_py(arg.type_().borrow()) -}}
+ {%- endmatch %}
+ {% endfor -%}
+{%- endmacro -%}
+
+{#
+ # Exactly the same thing as `setup_args()` but with an extra 4 spaces of
+ # indent so that it works with object methods.
+ #}
+{%- macro setup_args_extra_indent(func) %}
+ {%- for arg in func.arguments() %}
+ {%- match arg.default_value() %}
+ {%- when None %}
+ {{ arg.name()|var_name }} = {{ arg.name()|var_name|coerce_py(arg.type_().borrow()) -}}
+ {%- when Some with(literal) %}
+ if {{ arg.name()|var_name }} is DEFAULT:
+ {{ arg.name()|var_name }} = {{ literal|literal_py(arg.type_().borrow()) }}
+ else:
+ {{ arg.name()|var_name }} = {{ arg.name()|var_name|coerce_py(arg.type_().borrow()) -}}
+ {%- endmatch %}
+ {% endfor -%}
+{%- endmacro -%}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/templates/wrapper.py b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/wrapper.py
new file mode 100644
index 0000000000..d30d3c9d12
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/templates/wrapper.py
@@ -0,0 +1,71 @@
+# This file was autogenerated by some hot garbage in the `uniffi` crate.
+# Trust me, you don't want to mess with it!
+
+# Tell mypy (a type checker) to ignore all errors from this file.
+# See https://mypy.readthedocs.io/en/stable/config_file.html?highlight=ignore-errors#confval-ignore_errors
+# mypy: ignore-errors
+
+# Common helper code.
+#
+# Ideally this would live in a separate .py file where it can be unittested etc
+# in isolation, and perhaps even published as a re-useable package.
+#
+# However, it's important that the details of how this helper code works (e.g. the
+# way that different builtin types are passed across the FFI) exactly match what's
+# expected by the rust code on the other side of the interface. In practice right
+# now that means coming from the exact some version of `uniffi` that was used to
+# compile the rust component. The easiest way to ensure this is to bundle the Python
+# helpers directly inline like we're doing here.
+
+import os
+import sys
+import ctypes
+import enum
+import struct
+import contextlib
+import datetime
+{%- for req in self.imports() %}
+{{ req.render() }}
+{%- endfor %}
+
+# Used for default argument values
+DEFAULT = object()
+
+{% include "RustBufferTemplate.py" %}
+{% include "Helpers.py" %}
+{% include "RustBufferHelper.py" %}
+
+# Contains loading, initialization code,
+# and the FFI Function declarations in a com.sun.jna.Library.
+{% include "NamespaceLibraryTemplate.py" %}
+
+# Public interface members begin here.
+{{ type_helper_code }}
+
+{%- for func in ci.function_definitions() %}
+{%- include "TopLevelFunctionTemplate.py" %}
+{%- endfor %}
+
+__all__ = [
+ "InternalError",
+ {%- for e in ci.enum_definitions() %}
+ "{{ e|type_name }}",
+ {%- endfor %}
+ {%- for record in ci.record_definitions() %}
+ "{{ record|type_name }}",
+ {%- endfor %}
+ {%- for func in ci.function_definitions() %}
+ "{{ func.name()|fn_name }}",
+ {%- endfor %}
+ {%- for obj in ci.object_definitions() %}
+ "{{ obj|type_name }}",
+ {%- endfor %}
+ {%- for e in ci.error_definitions() %}
+ "{{ e|type_name }}",
+ {%- endfor %}
+ {%- for c in ci.callback_interface_definitions() %}
+ "{{ c.name()|class_name }}",
+ {%- endfor %}
+]
+
+{% import "macros.py" as py %}
diff --git a/third_party/rust/uniffi_bindgen/src/bindings/python/test.rs b/third_party/rust/uniffi_bindgen/src/bindings/python/test.rs
new file mode 100644
index 0000000000..d050fc0389
--- /dev/null
+++ b/third_party/rust/uniffi_bindgen/src/bindings/python/test.rs
@@ -0,0 +1,62 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use anyhow::{Context, Result};
+use camino::Utf8Path;
+use std::env;
+use std::ffi::OsString;
+use std::process::{Command, Stdio};
+use uniffi_testing::UniFFITestHelper;
+
+/// Run Python tests for a UniFFI test fixture
+pub fn run_test(tmp_dir: &str, fixture_name: &str, script_file: &str) -> Result<()> {
+ let script_path = Utf8Path::new(".").join(script_file).canonicalize_utf8()?;
+ let test_helper = UniFFITestHelper::new(fixture_name).context("UniFFITestHelper::new")?;
+ let out_dir = test_helper
+ .create_out_dir(tmp_dir, &script_path)
+ .context("create_out_dir")?;
+ test_helper
+ .copy_cdylibs_to_out_dir(&out_dir)
+ .context("copy_cdylibs_to_out_dir")?;
+ generate_sources(&test_helper.cdylib_path()?, &out_dir, &test_helper)
+ .context("generate_sources")?;
+
+ let pythonpath = env::var_os("PYTHONPATH").unwrap_or_else(|| OsString::from(""));
+ let pythonpath = env::join_paths(
+ env::split_paths(&pythonpath).chain(vec![out_dir.to_path_buf().into_std_path_buf()]),
+ )?;
+
+ let status = Command::new("python3")
+ .current_dir(out_dir)
+ .env("PYTHONPATH", pythonpath)
+ .arg(script_path)
+ .stderr(Stdio::inherit())
+ .stdout(Stdio::inherit())
+ .spawn()
+ .context("Failed to spawn `python3` when running script")?
+ .wait()
+ .context("Failed to wait for `python3` when running script")?;
+ if !status.success() {
+ anyhow::bail!("running `python3` failed");
+ }
+ Ok(())
+}
+
+fn generate_sources(
+ library_path: &Utf8Path,
+ out_dir: &Utf8Path,
+ test_helper: &UniFFITestHelper,
+) -> Result<()> {
+ for source in test_helper.get_compile_sources()? {
+ crate::generate_bindings(
+ &source.udl_path,
+ source.config_path.as_deref(),
+ vec!["python"],
+ Some(out_dir),
+ Some(library_path),
+ false,
+ )?;
+ }
+ Ok(())
+}