bindgen: determinism

This commit is contained in:
nym21
2026-01-27 23:48:19 +01:00
parent 730e83472a
commit fecaf0f400
21 changed files with 787 additions and 739 deletions
@@ -7,8 +7,9 @@ pub mod client;
pub mod tree;
pub mod types;
use std::{fmt::Write, fs, io, path::Path};
use std::{fmt::Write, io, path::Path};
use super::write_if_changed;
use crate::{ClientMetadata, Endpoint, TypeSchemas};
/// Generate Python client from metadata and OpenAPI endpoints.
@@ -48,7 +49,7 @@ pub fn generate_python_client(
tree::generate_tree_classes(&mut output, &metadata.catalog, metadata);
api::generate_main_client(&mut output, endpoints);
fs::write(output_path, output)?;
write_if_changed(output_path, &output)?;
Ok(())
}
@@ -1,6 +1,6 @@
//! Python tree structure generation.
use std::collections::HashSet;
use std::collections::BTreeSet;
use std::fmt::Write;
use brk_types::TreeNode;
@@ -15,7 +15,7 @@ pub fn generate_tree_classes(output: &mut String, catalog: &TreeNode, metadata:
writeln!(output, "# Metrics tree classes\n").unwrap();
let pattern_lookup = metadata.pattern_lookup();
let mut generated = HashSet::new();
let mut generated = BTreeSet::new();
generate_tree_class(
output,
"MetricsTree",
@@ -33,9 +33,9 @@ fn generate_tree_class(
name: &str,
path: &str,
node: &TreeNode,
pattern_lookup: &std::collections::HashMap<Vec<PatternField>, String>,
pattern_lookup: &std::collections::BTreeMap<Vec<PatternField>, String>,
metadata: &ClientMetadata,
generated: &mut HashSet<String>,
generated: &mut BTreeSet<String>,
) {
let Some(ctx) = prepare_tree_node(node, name, path, pattern_lookup, metadata, generated) else {
return;
@@ -74,7 +74,9 @@ fn generate_tree_class(
if child.is_leaf {
if let TreeNode::Leaf(leaf) = child.node {
generate_leaf_field(output, &syntax, "client", child.name, leaf, metadata, " ");
generate_leaf_field(
output, &syntax, "client", child.name, leaf, metadata, " ",
);
}
} else if child.should_inline {
// Inline class
@@ -1,11 +1,14 @@
//! Python type definitions generation.
use std::collections::{HashMap, HashSet};
use std::collections::{BTreeMap, BTreeSet};
use std::fmt::Write;
use serde_json::Value;
use crate::{TypeSchemas, escape_python_keyword, generators::MANUAL_GENERIC_TYPES, get_union_variants, ref_to_type_name};
use crate::{
TypeSchemas, escape_python_keyword, generators::MANUAL_GENERIC_TYPES, get_union_variants,
ref_to_type_name,
};
/// Generate type definitions from schemas.
pub fn generate_type_definitions(output: &mut String, schemas: &TypeSchemas) {
@@ -32,7 +35,7 @@ pub fn generate_type_definitions(output: &mut String, schemas: &TypeSchemas) {
// Generate simple type aliases first
// Quote references to TypedDicts since they're defined after
let typed_dict_set: HashSet<_> = typed_dicts.iter().cloned().collect();
let typed_dict_set: BTreeSet<_> = typed_dicts.iter().cloned().collect();
for name in type_aliases {
let schema = &schemas[&name];
let type_desc = schema.get("description").and_then(|d| d.as_str());
@@ -49,7 +52,10 @@ pub fn generate_type_definitions(output: &mut String, schemas: &TypeSchemas) {
for name in typed_dicts {
let schema = &schemas[&name];
let type_desc = schema.get("description").and_then(|d| d.as_str());
let props = schema.get("properties").and_then(|p| p.as_object()).unwrap();
let props = schema
.get("properties")
.and_then(|p| p.as_object())
.unwrap();
writeln!(output, "class {}(TypedDict):", name).unwrap();
@@ -100,9 +106,9 @@ pub fn generate_type_definitions(output: &mut String, schemas: &TypeSchemas) {
/// Types that reference other types (via $ref) must be defined after their dependencies.
fn topological_sort_schemas(schemas: &TypeSchemas) -> Vec<String> {
// Build dependency graph
let mut deps: HashMap<String, HashSet<String>> = HashMap::new();
let mut deps: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
for (name, schema) in schemas {
let mut type_deps = HashSet::new();
let mut type_deps = BTreeSet::new();
collect_schema_refs(schema, &mut type_deps);
// Only keep deps that are in our schemas
type_deps.retain(|d| schemas.contains_key(d));
@@ -110,7 +116,7 @@ fn topological_sort_schemas(schemas: &TypeSchemas) -> Vec<String> {
}
// Kahn's algorithm for topological sort
let mut in_degree: HashMap<String, usize> = HashMap::new();
let mut in_degree: BTreeMap<String, usize> = BTreeMap::new();
for name in schemas.keys() {
in_degree.insert(name.clone(), 0);
}
@@ -148,7 +154,7 @@ fn topological_sort_schemas(schemas: &TypeSchemas) -> Vec<String> {
result.reverse();
// Add any types that weren't processed (e.g., due to circular refs or other edge cases)
let result_set: HashSet<_> = result.iter().cloned().collect();
let result_set: BTreeSet<_> = result.iter().cloned().collect();
let mut missing: Vec<_> = schemas
.keys()
.filter(|k| !result_set.contains(*k))
@@ -161,7 +167,7 @@ fn topological_sort_schemas(schemas: &TypeSchemas) -> Vec<String> {
}
/// Collect all type references ($ref) from a schema
fn collect_schema_refs(schema: &Value, refs: &mut HashSet<String>) {
fn collect_schema_refs(schema: &Value, refs: &mut BTreeSet<String>) {
match schema {
Value::Object(map) => {
if let Some(ref_path) = map.get("$ref").and_then(|r| r.as_str())
@@ -215,7 +221,7 @@ fn json_type_to_python(ty: &str, schema: &Value, current_type: Option<&str>) ->
pub fn schema_to_python_type(
schema: &Value,
current_type: Option<&str>,
quote_types: Option<&HashSet<String>>,
quote_types: Option<&BTreeSet<String>>,
) -> String {
if let Some(all_of) = schema.get("allOf").and_then(|v| v.as_array()) {
for item in all_of {
@@ -230,8 +236,8 @@ pub fn schema_to_python_type(
if let Some(ref_path) = schema.get("$ref").and_then(|r| r.as_str()) {
let type_name = ref_to_type_name(ref_path).unwrap_or("Any");
// Quote self-references or types in quote_types set
let should_quote = current_type == Some(type_name)
|| quote_types.is_some_and(|qt| qt.contains(type_name));
let should_quote =
current_type == Some(type_name) || quote_types.is_some_and(|qt| qt.contains(type_name));
if should_quote {
return format!("\"{}\"", type_name);
}