diff --git a/crates/README.md b/crates/README.md index 52618c87d0..d2172a022a 100644 --- a/crates/README.md +++ b/crates/README.md @@ -70,7 +70,15 @@ The same check fails when the workspace version, the `pyproject.toml` version an `import(MODULE)?.getattr("name")`, or written as a `("sqlbuild.module", "name")` pair; - module paths are never built at runtime: a `format!` or `concat!` template may not start with `sqlbuild.` or with a `{..PACKAGE..}.`/`{..MODULE..}.` constant. Write the complete module - path as a constant instead. + path as a constant instead; +- every row of the host table (`bindings/_helpers/native_host/`) must resolve as an attribute of + `sqlbuild._native_host`. + +Staying-Python entry points (adapter, user code, executor and runtime, third-party and public +facade) are reached through the host table and `native_host::host::entry`, which imports only +`sqlbuild._native_host`. Classes, enums, errors and functions `sqlbuild._native` registers are +reached through the typed registry (`records::registry` with `RecordId`, `ErrorId` and +`NativeAttr`), never by module path. ## Compiler diff --git a/crates/sqlbuild-analysis/src/compiler/_helpers/sql_tests/check_independence.rs b/crates/sqlbuild-analysis/src/compiler/_helpers/sql_tests/check_independence.rs index 32e895995e..ae53d08a11 100644 --- a/crates/sqlbuild-analysis/src/compiler/_helpers/sql_tests/check_independence.rs +++ b/crates/sqlbuild-analysis/src/compiler/_helpers/sql_tests/check_independence.rs @@ -1,6 +1,8 @@ //! Expected results and assertions must not define or read each other, even through helpers. -use std::collections::{BTreeSet, HashMap, HashSet, VecDeque}; +use std::collections::{BTreeSet, HashMap}; + +use sqlbuild_core::graph_walk::main::breadth_first_path::breadth_first_path; use polyglot_sql::{Dialect, DialectType, Expression, ExpressionWalk}; use serde_json::Value; @@ -246,20 +248,12 @@ fn dependency_path( prohibited: &BTreeSet, dependencies: &HashMap>, ) -> Option> { - let mut pending: VecDeque> = VecDeque::from([vec![origin.to_owned()]]); - let mut visited: HashSet = HashSet::from([origin.to_owned()]); - while let Some(path) = pending.pop_front() { - let last: &String = path.last()?; - for dependency in dependencies.get(last).into_iter().flatten() { - let mut candidate: Vec = path.clone(); - candidate.push(dependency.clone()); - if prohibited.contains(dependency) { - return Some(candidate); - } - if visited.insert(dependency.clone()) { - pending.push_back(candidate); - } - } - } - None + breadth_first_path( + origin.to_owned(), + ( + |last: &String| dependencies.get(last).into_iter().flatten().cloned(), + |dependency: &String| prohibited.contains(dependency), + |_: &String| true, + ), + ) } diff --git a/crates/sqlbuild-analysis/src/compiler/_helpers/sql_tests/helper_scope.rs b/crates/sqlbuild-analysis/src/compiler/_helpers/sql_tests/helper_scope.rs index 21c5ee7c27..dc7af49e4f 100644 --- a/crates/sqlbuild-analysis/src/compiler/_helpers/sql_tests/helper_scope.rs +++ b/crates/sqlbuild-analysis/src/compiler/_helpers/sql_tests/helper_scope.rs @@ -2,6 +2,8 @@ use std::collections::{BTreeMap, HashMap, HashSet}; +use sqlbuild_core::graph_walk::main::depth_first_postorder::depth_first_postorder; + use polyglot_sql::Dialect; use crate::compiler::_helpers::sql_tests::cte_rename::defined_cte_keys; @@ -103,36 +105,6 @@ pub(crate) struct ScopeGraph { rewritten: HashMap, } -/// Graph nodes in dependency-first order, built by depth-first traversal. -struct ScopeOrder<'a> { - graph: &'a ScopeGraph, - visited: HashSet, - ordered: Vec, - through_mocks: bool, -} - -impl ScopeOrder<'_> { - fn visit_tokens(&mut self, tokens: &[String]) { - for token in tokens { - if let Some(node) = self.graph.node(token) { - if !self.through_mocks && matches!(node, ScopeNode::Mock(_)) { - continue; - } - self.visit(node); - } - } - } - - fn visit(&mut self, node: ScopeNode) { - if !self.visited.insert(node) { - return; - } - let graph = self.graph; - self.visit_tokens(graph.tokens(node)); - self.ordered.push(node); - } -} - impl ScopeGraph { /// Tokenize one test's helpers and mocks and decide how its helpers are named and placed. pub(crate) fn new(request: &ScopeRequest<'_>) -> Result { @@ -294,14 +266,14 @@ impl ScopeGraph { } fn closure(&self, tokens: &[String], through_mocks: bool) -> Vec { - let mut order = ScopeOrder { - graph: self, - visited: HashSet::new(), - ordered: Vec::new(), - through_mocks, + let entered = |tokens: &[String]| -> Vec { + tokens + .iter() + .filter_map(|token| self.node(token)) + .filter(|node| through_mocks || !matches!(node, ScopeNode::Mock(_))) + .collect() }; - order.visit_tokens(tokens); - order.ordered + depth_first_postorder(entered(tokens), |node| entered(self.tokens(*node))) } /// Indexes of the helpers the compiler found the test's expected rows and assertions read. diff --git a/crates/sqlbuild-analysis/src/compiler/_helpers/sql_tests/mock_reads.rs b/crates/sqlbuild-analysis/src/compiler/_helpers/sql_tests/mock_reads.rs index 941617bf68..97e790075e 100644 --- a/crates/sqlbuild-analysis/src/compiler/_helpers/sql_tests/mock_reads.rs +++ b/crates/sqlbuild-analysis/src/compiler/_helpers/sql_tests/mock_reads.rs @@ -1,11 +1,15 @@ //! Python's `report_mocks_reading_referencing_helpers`: P013s for mocks reading referencing helpers. use std::collections::HashSet; +use std::convert::Infallible; use polyglot_sql::expressions::With; use polyglot_sql::{ ComplexityGuardOptions, Dialect, DialectType, Expression, ExpressionWalk, ParseOptions, }; +use sqlbuild_core::graph_walk::main::breadth_first_reach::breadth_first_reach; +use sqlbuild_core::graph_walk::models::VisitOrder; +use sqlbuild_core::graph_walk::types::NodeSet; use sqlbuild_core::text::main::ignorecase_key::ignorecase_key; use sqlbuild_core::text::main::is_word_char::is_word_char; use sqlbuild_sqltext::sql_references::main::extract_sql_references::extract_sql_references; @@ -111,30 +115,18 @@ impl CteGraph<'_> { /// Python's `reachable_cte_keys`: breadth first, roots included, in visit order. fn reachable(&self, roots: &[String]) -> Vec { - let mut pending: Vec = Vec::new(); - for root in roots { - if self.cte(root).is_some() { - pending.push(root.clone()); - } - } - let mut visited: Vec = Vec::new(); - for key in &pending { - if !visited.contains(key) { - visited.push(key.clone()); - } - } - let mut cursor = 0; - while cursor < pending.len() { - let key = pending[cursor].clone(); - cursor += 1; - for dependency in self.reads(&key) { - if !visited.contains(dependency) { - visited.push(dependency.clone()); - pending.push(dependency.clone()); - } - } + let known: Vec<&String> = roots + .iter() + .filter(|root| self.cte(root).is_some()) + .collect(); + let mut visited: VisitOrder<&String> = VisitOrder::default(); + for root in &known { + let Ok(_) = visited.insert_new(root); } - visited + let Ok(()) = breadth_first_reach(known, None, &mut visited, |key| { + Ok::<_, Infallible>(self.reads(key)) + }); + visited.order.into_iter().cloned().collect() } } diff --git a/crates/sqlbuild-analysis/src/compiler/_helpers/sql_tests/planning.rs b/crates/sqlbuild-analysis/src/compiler/_helpers/sql_tests/planning.rs index 272ae72a03..12a44c5c3e 100644 --- a/crates/sqlbuild-analysis/src/compiler/_helpers/sql_tests/planning.rs +++ b/crates/sqlbuild-analysis/src/compiler/_helpers/sql_tests/planning.rs @@ -1,6 +1,7 @@ //! Coarse project-level planning for SQL-native test artifacts. use std::collections::{BTreeMap, HashMap, HashSet}; +use std::convert::Infallible; use std::sync::{Arc, Mutex, OnceLock, PoisonError}; use std::time::Instant; @@ -9,6 +10,8 @@ use rayon::iter::{IntoParallelIterator, ParallelIterator}; use regex::Regex; #[cfg(test)] use serde::Serialize; +use sqlbuild_core::graph_walk::main::breadth_first_reach::breadth_first_reach; +use sqlbuild_core::graph_walk::main::depth_first_postorder::depth_first_postorder; use crate::compiler::_helpers::sql_tests::cte_rename::defined_cte_keys; use crate::compiler::_helpers::sql_tests::cte_slices::{SliceDialect, strip_statement_terminators}; @@ -256,22 +259,12 @@ impl TextualChain { /// Return the generated CTEs needed by `roots`, dependencies first, each exactly once. fn closure_ctes(&self, roots: &[String], include_roots: bool) -> Vec<(String, String)> { let mut needed: HashSet = HashSet::new(); - let mut stack: Vec = Vec::new(); - for root in roots { - if include_roots { - stack.push(root.clone()); - } else if let Some(dependencies) = self.dependencies.get(root) { - stack.extend(dependencies.iter().cloned()); - } - } - while let Some(name) = stack.pop() { - if !needed.insert(name.clone()) { - continue; - } - if let Some(dependencies) = self.dependencies.get(&name) { - stack.extend(dependencies.iter().cloned()); - } + if include_roots { + needed.extend(roots.iter().cloned()); } + let Ok(()) = breadth_first_reach(roots.iter().cloned(), None, &mut needed, |name| { + Ok::<_, Infallible>(self.dependencies.get(name).into_iter().flatten().cloned()) + }); let mut ctes: Vec<(String, String)> = with_unique_ctes( Vec::new(), self.order @@ -1310,60 +1303,27 @@ fn classify_fixtures( } fn topo_sort_model_chain(request: TopoSortRequest<'_>) -> Vec { - fn visit( - name: &str, - models: &HashMap, - overrides: &BTreeMap, - mock_refs: &BTreeMap, - patterns: &SqlTestPatterns, - visited: &mut HashSet, - ordered: &mut Vec, - ) { - if !visited.insert(name.to_string()) { - return; - } - if let Some(model) = models.get(name) { - let mut dependencies = if let Some(override_sql) = overrides.get(name) { - marker_names(&patterns.reference, &patterns.lexical, override_sql) - } else { - model.model_dependencies.clone() - }; - dependencies.retain(|dependency| { - !mock_refs.contains_key(dependency) && models.contains_key(dependency) - }); - dependencies.sort(); - dependencies.dedup(); - for dependency in dependencies { - visit( - &dependency, - models, - overrides, - mock_refs, - patterns, - visited, - ordered, - ); - } - } - ordered.push(name.to_string()); - } - - let mut roots = request.expected_names.to_vec(); + let mut roots: Vec = request.expected_names.to_vec(); roots.sort(); - let mut visited: HashSet = HashSet::new(); - let mut ordered: Vec = Vec::new(); - for root in roots { - visit( - &root, - request.models, - request.overrides, - request.mock_refs, - request.patterns, - &mut visited, - &mut ordered, - ); - } - ordered + depth_first_postorder(roots, |name| { + let Some(model) = request.models.get(name) else { + return Vec::new(); + }; + let mut dependencies: Vec = match request.overrides.get(name) { + Some(override_sql) => marker_names( + &request.patterns.reference, + &request.patterns.lexical, + override_sql, + ), + None => model.model_dependencies.clone(), + }; + dependencies.retain(|dependency| { + !request.mock_refs.contains_key(dependency) && request.models.contains_key(dependency) + }); + dependencies.sort(); + dependencies.dedup(); + dependencies + }) } fn analyze_and_resolve_sql( diff --git a/crates/sqlbuild-analysis/src/dag_artifact/_helpers/selection.rs b/crates/sqlbuild-analysis/src/dag_artifact/_helpers/selection.rs index 8148ad5255..dd307490d5 100644 --- a/crates/sqlbuild-analysis/src/dag_artifact/_helpers/selection.rs +++ b/crates/sqlbuild-analysis/src/dag_artifact/_helpers/selection.rs @@ -1,7 +1,10 @@ -//! Breadth-first closures and directed path nodes over string-keyed DAG edges. +//! Closures, directed path nodes and selector resolution over string-keyed DAG edges. -use std::collections::{BTreeMap, BTreeSet, VecDeque}; +use std::collections::{BTreeMap, BTreeSet}; +use std::convert::Infallible; +use sqlbuild_core::graph_walk::main::breadth_first_reach::breadth_first_reach; +use sqlbuild_core::graph_walk::main::directed_path_nodes::directed_path_nodes; use sqlbuild_core::text::main::python_isspace::python_isspace; use sqlbuild_core::text::main::python_str_repr::python_str_repr; @@ -23,20 +26,14 @@ pub(crate) fn closure( edges: &Adjacency, include_starts: bool, ) -> BTreeSet { - let mut visited: BTreeSet = if include_starts { - starts.clone() - } else { - BTreeSet::new() - }; - let mut frontier: VecDeque<&String> = starts.iter().collect(); - while let Some(current) = frontier.pop_front() { - for neighbor in edges.get(current).into_iter().flatten() { - if visited.insert(neighbor.clone()) { - frontier.push_back(neighbor); - } - } + let mut visited: BTreeSet<&String> = BTreeSet::new(); + if include_starts { + visited.extend(starts); } - visited + let Ok(()) = breadth_first_reach(starts.iter(), None, &mut visited, |current| { + Ok::<_, Infallible>(edges.get(*current).into_iter().flatten()) + }); + visited.into_iter().cloned().collect() } /// Ids on directed paths from `start` to `end`, or `None` when `end` is not downstream. @@ -44,27 +41,18 @@ pub(crate) fn path_nodes( (start, end): (&str, &str), (upstream, downstream): (&Adjacency, &Adjacency), ) -> Option> { - let reachable: BTreeSet = - closure(&BTreeSet::from([start.to_owned()]), downstream, false); - if !reachable.contains(end) { - return None; - } - let mut upstream_of_end: BTreeSet = BTreeSet::new(); - let mut stack: Vec = vec![end.to_owned()]; - while let Some(current) = stack.pop() { - if !upstream_of_end.insert(current.clone()) { - continue; - } - for parent in upstream.get(¤t).into_iter().flatten() { - if reachable.contains(parent) || parent == start { - stack.push(parent.clone()); - } - } - } - let mut nodes: BTreeSet = reachable.intersection(&upstream_of_end).cloned().collect(); - nodes.insert(start.to_owned()); - nodes.insert(end.to_owned()); - Some(nodes) + let (start, end): (String, String) = (start.to_owned(), end.to_owned()); + let (mut reachable, mut path): (BTreeSet<&String>, BTreeSet<&String>) = + (BTreeSet::new(), BTreeSet::new()); + let Ok(found) = directed_path_nodes( + (&start, &end), + ( + |current: &&String| Ok(downstream.get(*current).into_iter().flatten()), + |current: &&String| Ok(upstream.get(*current).into_iter().flatten()), + ), + (&mut reachable, &mut path), + ); + found.then(|| path.into_iter().cloned().collect()) } pub(crate) fn resolve_expression( diff --git a/crates/sqlbuild-analysis/src/diff_selection/_helpers/tokens.rs b/crates/sqlbuild-analysis/src/diff_selection/_helpers/tokens.rs index 6c810daa23..b23c303398 100644 --- a/crates/sqlbuild-analysis/src/diff_selection/_helpers/tokens.rs +++ b/crates/sqlbuild-analysis/src/diff_selection/_helpers/tokens.rs @@ -1,6 +1,9 @@ //! One diff selector token resolved to the resources it names. -use std::collections::{HashSet, VecDeque}; +use std::collections::HashSet; +use std::convert::Infallible; + +use sqlbuild_core::graph_walk::main::breadth_first_reach::breadth_first_reach; use crate::diff_selection::constants::{ EMPTY_SELECTOR_CODE, GRAPH_OPERATOR, INTERSECTION_SEPARATOR, MODEL_PATH_PREFIX, @@ -13,14 +16,9 @@ use crate::diff_selection::models::DiffGraph; /// Resources reachable from `starts` along `edges`, without the starts unless reached. fn reachable(starts: &HashSet, edges: &[Vec]) -> HashSet { let mut visited: HashSet = HashSet::new(); - let mut frontier: VecDeque = starts.iter().copied().collect(); - while let Some(current) = frontier.pop_front() { - for neighbor in edges.get(current).into_iter().flatten() { - if visited.insert(*neighbor) { - frontier.push_back(*neighbor); - } - } - } + let Ok(()) = breadth_first_reach(starts.iter().copied(), None, &mut visited, |current| { + Ok::<_, Infallible>(edges.get(*current).into_iter().flatten().copied()) + }); visited } diff --git a/crates/sqlbuild-analysis/src/explicit_references/_helpers/graph.rs b/crates/sqlbuild-analysis/src/explicit_references/_helpers/graph.rs index 337a3f0ade..0852dec795 100644 --- a/crates/sqlbuild-analysis/src/explicit_references/_helpers/graph.rs +++ b/crates/sqlbuild-analysis/src/explicit_references/_helpers/graph.rs @@ -1,6 +1,8 @@ //! Python's lineage upstream edges and transitive closure over compiled resource keys. -use std::collections::{HashMap, HashSet, VecDeque}; +use std::collections::HashMap; + +use sqlbuild_core::graph_walk::main::breadth_first_path::breadth_first_path; use crate::explicit_references::constants::SQL_TEST_TYPE; use crate::explicit_references::models::GraphResource; @@ -31,17 +33,13 @@ pub(crate) fn lineage_upstream(resources: &[GraphResource]) -> Upstream { /// Whether `target` is reachable from `start` over at least one upstream edge. pub(crate) fn reaches(upstream: &Upstream, start: &ResourceKey, target: &ResourceKey) -> bool { - let mut visited: HashSet<&ResourceKey> = HashSet::new(); - let mut frontier: VecDeque<&ResourceKey> = VecDeque::from([start]); - while let Some(current) = frontier.pop_front() { - for neighbor in upstream.get(current).into_iter().flatten() { - if visited.insert(neighbor) { - if neighbor == target { - return true; - } - frontier.push_back(neighbor); - } - } - } - false + breadth_first_path( + start, + ( + |current: &&ResourceKey| upstream.get(*current).into_iter().flatten(), + |neighbour: &&ResourceKey| *neighbour == target, + |_: &&ResourceKey| true, + ), + ) + .is_some() } diff --git a/crates/sqlbuild-analysis/src/graph/_helpers/closure.rs b/crates/sqlbuild-analysis/src/graph/_helpers/closure.rs index 1c9360819b..8466a44518 100644 --- a/crates/sqlbuild-analysis/src/graph/_helpers/closure.rs +++ b/crates/sqlbuild-analysis/src/graph/_helpers/closure.rs @@ -1,6 +1,10 @@ -//! Python's breadth-first transitive closure and directed path nodes over graph edges. +//! Transitive closure and directed path nodes over project graph edges. -use std::collections::{BTreeSet, HashSet, VecDeque}; +use std::collections::{BTreeSet, HashSet}; +use std::convert::Infallible; + +use sqlbuild_core::graph_walk::main::breadth_first_reach::breadth_first_reach; +use sqlbuild_core::graph_walk::main::directed_path_nodes::directed_path_nodes; use crate::assembly::project::types::ObjectKey; use crate::graph::models::{EdgeDirection as Direction, ProjectGraph}; @@ -19,14 +23,9 @@ impl ProjectGraph { /// Every key reachable from `start`; `start` itself only through a cycle. pub(crate) fn closure(&self, start: &ObjectKey, direction: Direction) -> BTreeSet { let mut visited: HashSet<&ObjectKey> = HashSet::new(); - let mut frontier: VecDeque<&ObjectKey> = VecDeque::from([start]); - while let Some(current) = frontier.pop_front() { - for neighbor in self.edges(current, direction) { - if visited.insert(neighbor) { - frontier.push_back(neighbor); - } - } - } + let Ok(()) = breadth_first_reach([start], None, &mut visited, |current| { + Ok::<_, Infallible>(self.edges(current, direction)) + }); visited.into_iter().cloned().collect() } @@ -36,35 +35,25 @@ impl ProjectGraph { start: &ObjectKey, end: &ObjectKey, ) -> Option> { - let reachable: BTreeSet = self.closure(start, Direction::Downstream); - if !reachable.contains(end) { - return None; - } - let mut upstream_of_end: BTreeSet = BTreeSet::new(); - let mut stack: Vec = vec![end.clone()]; - while let Some(current) = stack.pop() { - if !upstream_of_end.insert(current.clone()) { - continue; - } - for parent in self.downstream_parents(¤t) { - if reachable.contains(&parent) || &parent == start { - stack.push(parent); - } - } - } - let mut nodes: BTreeSet = - reachable.intersection(&upstream_of_end).cloned().collect(); - nodes.insert(start.clone()); - nodes.insert(end.clone()); - Some(nodes) + let (mut reachable, mut path): (HashSet<&ObjectKey>, HashSet<&ObjectKey>) = + (HashSet::new(), HashSet::new()); + let Ok(found) = directed_path_nodes( + (start, end), + ( + |current: &&ObjectKey| Ok(self.edges(current, Direction::Downstream)), + |current: &&ObjectKey| Ok(self.downstream_parents(current)), + ), + (&mut reachable, &mut path), + ); + found.then(|| path.into_iter().cloned().collect()) } /// Keys whose downstream list holds `key`: the inverse of the downstream edges. - fn downstream_parents(&self, key: &ObjectKey) -> Vec { + fn downstream_parents<'graph>(&'graph self, key: &ObjectKey) -> Vec<&'graph ObjectKey> { self.downstream .iter() .filter(|(_, children)| children.contains(key)) - .map(|(parent, _)| parent.clone()) + .map(|(parent, _)| parent) .collect() } } diff --git a/crates/sqlbuild-analysis/src/lineage_command/_helpers/graph.rs b/crates/sqlbuild-analysis/src/lineage_command/_helpers/graph.rs index 7f5dd861fe..a2f500be5f 100644 --- a/crates/sqlbuild-analysis/src/lineage_command/_helpers/graph.rs +++ b/crates/sqlbuild-analysis/src/lineage_command/_helpers/graph.rs @@ -1,6 +1,9 @@ -//! Breadth-first closures and selected-graph assembly over a lineage index. +//! Closures and selected-graph assembly over a lineage index. -use std::collections::{HashMap, HashSet, VecDeque}; +use std::collections::{HashMap, HashSet}; +use std::convert::Infallible; + +use sqlbuild_core::graph_walk::main::breadth_first_reach::breadth_first_reach; use crate::lineage_command::constants::{ BOTH_DIRECTIONS, DOWNSTREAM_DIRECTION, UPSTREAM_DIRECTION, @@ -24,20 +27,14 @@ pub(crate) fn closure( starts: &[LineageKey], max_depth: Option, ) -> HashSet { - let mut visited: HashSet = HashSet::new(); - let mut frontier: VecDeque<(&LineageKey, i64)> = - starts.iter().map(|start| (start, 0)).collect(); - while let Some((current, depth)) = frontier.pop_front() { - if max_depth.is_some_and(|maximum| depth >= maximum) { - continue; - } - for neighbor in edges.get(current).copied().unwrap_or_default() { - if visited.insert(neighbor.clone()) { - frontier.push_back((neighbor, depth + 1)); - } - } - } - visited + let mut visited: HashSet<&LineageKey> = HashSet::new(); + let Ok(()) = breadth_first_reach( + starts, + max_depth.map(|maximum| u64::try_from(maximum).unwrap_or(0)), + &mut visited, + |current| Ok::<_, Infallible>(edges.get(*current).copied().unwrap_or_default()), + ); + visited.into_iter().cloned().collect() } /// The keys `direction` reaches from `keys` within `depth`. diff --git a/crates/sqlbuild-analysis/src/lineage_command/main/lineage_path_nodes.rs b/crates/sqlbuild-analysis/src/lineage_command/main/lineage_path_nodes.rs index 6ce7bb0db0..4a21bf0e22 100644 --- a/crates/sqlbuild-analysis/src/lineage_command/main/lineage_path_nodes.rs +++ b/crates/sqlbuild-analysis/src/lineage_command/main/lineage_path_nodes.rs @@ -2,7 +2,9 @@ use std::collections::HashSet; -use crate::lineage_command::_helpers::graph::{EdgeMap, closure, edge_map}; +use sqlbuild_core::graph_walk::main::directed_path_nodes::directed_path_nodes; + +use crate::lineage_command::_helpers::graph::{EdgeMap, edge_map}; use crate::lineage_command::models::{LineageIndex, LineageKey}; /// Keys on downstream paths from `start` to `end`, or none when `end` is not downstream. @@ -13,28 +15,25 @@ pub fn lineage_path_nodes( end: &LineageKey, ) -> Vec { let downstream: EdgeMap<'_> = edge_map(&index.downstream); - let reachable: HashSet = closure(&downstream, std::slice::from_ref(start), None); - if !reachable.contains(end) { + let (mut reachable, mut path): (HashSet<&LineageKey>, HashSet<&LineageKey>) = + (HashSet::new(), HashSet::new()); + let Ok(found) = directed_path_nodes( + (start, end), + ( + |current: &&LineageKey| Ok(downstream.get(*current).copied().unwrap_or_default()), + |current: &&LineageKey| { + Ok(index + .downstream + .iter() + .filter(|(_, children)| children.contains(*current)) + .map(|(parent, _)| parent) + .collect::>()) + }, + ), + (&mut reachable, &mut path), + ); + if !found { return Vec::new(); } - let mut upstream_of_end: HashSet<&LineageKey> = HashSet::new(); - let mut stack: Vec<&LineageKey> = vec![end]; - while let Some(current) = stack.pop() { - if !upstream_of_end.insert(current) { - continue; - } - for (parent, children) in &index.downstream { - if children.contains(current) && (reachable.contains(parent) || parent == start) { - stack.push(parent); - } - } - } - let mut nodes: Vec = reachable - .iter() - .filter(|key| upstream_of_end.contains(key)) - .cloned() - .collect(); - nodes.push(start.clone()); - nodes.push(end.clone()); - nodes + path.into_iter().cloned().collect() } diff --git a/crates/sqlbuild-analysis/src/planner_identity/main/topological_order.rs b/crates/sqlbuild-analysis/src/planner_identity/main/topological_order.rs index 6a183eb1d7..7c84421336 100644 --- a/crates/sqlbuild-analysis/src/planner_identity/main/topological_order.rs +++ b/crates/sqlbuild-analysis/src/planner_identity/main/topological_order.rs @@ -3,6 +3,8 @@ use std::cmp::Reverse; use std::collections::{BinaryHeap, HashMap}; +use sqlbuild_core::graph_walk::main::kahn_order::kahn_order; + use crate::planner_identity::_helpers::cycle_message::cycle_message; use crate::planner_identity::_helpers::dependency_counts::{DependencyCounts, dependency_counts}; use crate::planner_identity::models::GraphKey; @@ -14,26 +16,17 @@ pub fn topological_order( ) -> Result, String> { let DependencyCounts { node_keys, - mut indegree, + indegree, downstream, } = dependency_counts(upstream); - let mut ready: BinaryHeap> = indegree - .iter() - .filter(|(_, count)| **count == 0) - .map(|(key, _)| Reverse(*key)) - .collect(); - let mut ordered: Vec = Vec::with_capacity(node_keys.len()); - while let Some(Reverse(current)) = ready.pop() { - ordered.push(current.clone()); - for downstream_key in downstream.get(current).into_iter().flatten() { - if let Some(count) = indegree.get_mut(downstream_key) { - *count = count.saturating_sub(1); - if *count == 0 { - ready.push(Reverse(downstream_key)); - } - } - } - } + let ordered: Vec = kahn_order( + (node_keys.iter().copied(), indegree), + BinaryHeap::>::new(), + |current| downstream.get(*current).cloned().unwrap_or_default(), + ) + .into_iter() + .cloned() + .collect(); if ordered.len() == node_keys.len() { return Ok(ordered); } diff --git a/crates/sqlbuild-analysis/src/planner_migrations/main/discovery_order.rs b/crates/sqlbuild-analysis/src/planner_migrations/main/discovery_order.rs index 04c8d5fa44..e889e29765 100644 --- a/crates/sqlbuild-analysis/src/planner_migrations/main/discovery_order.rs +++ b/crates/sqlbuild-analysis/src/planner_migrations/main/discovery_order.rs @@ -1,31 +1,18 @@ //! Stable depth-first ordering for migration identity propagation. -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeMap; + +use sqlbuild_core::graph_walk::main::depth_first_postorder::depth_first_postorder; /// Order models after their model dependencies using lexical traversal ties. pub fn discovery_order(dependencies: &BTreeMap>) -> Vec { - let mut pending: Vec<(String, bool)> = dependencies - .keys() - .rev() - .map(|name| (name.clone(), false)) - .collect(); - let mut visited: BTreeSet = BTreeSet::new(); - let mut ordered: Vec = Vec::new(); - while let Some((name, expanded)) = pending.pop() { - if expanded { - ordered.push(name); - continue; - } - if !visited.insert(name.clone()) { - continue; - } - pending.push((name.clone(), true)); - let mut upstream = dependencies[&name].clone(); + depth_first_postorder(dependencies.keys().cloned(), |name| { + let mut upstream: Vec = dependencies[name] + .iter() + .filter(|dependency| dependencies.contains_key(*dependency)) + .cloned() + .collect(); upstream.sort(); - pending.extend(upstream.into_iter().rev().filter_map(|dependency| { - (dependencies.contains_key(&dependency) && !visited.contains(&dependency)) - .then_some((dependency, false)) - })); - } - ordered + upstream + }) } diff --git a/crates/sqlbuild-analysis/src/planner_scope/models.rs b/crates/sqlbuild-analysis/src/planner_scope/models.rs index 38a1602733..1e892d9e74 100644 --- a/crates/sqlbuild-analysis/src/planner_scope/models.rs +++ b/crates/sqlbuild-analysis/src/planner_scope/models.rs @@ -1,6 +1,11 @@ //! Ordered loader facts used by planner scope resolution. -use std::collections::{HashMap, HashSet, VecDeque}; +use std::collections::HashMap; +use std::convert::Infallible; + +use sqlbuild_core::graph_walk::main::breadth_first_reach::breadth_first_reach; +use sqlbuild_core::graph_walk::models::VisitOrder; +use sqlbuild_core::graph_walk::types::NodeSet; use crate::assembly::project::types::ObjectKey; use crate::planner_scope::constants::SOURCE; @@ -15,19 +20,14 @@ pub struct LoaderGraph { impl LoaderGraph { /// Upstream loader names in breadth-first, first-seen order. pub fn upstream(&self, dependencies: &[String]) -> Vec { - let mut pending: VecDeque = dependencies.iter().cloned().collect(); - let mut seen: HashSet = HashSet::new(); - let mut names: Vec = Vec::new(); - while let Some(name) = pending.pop_front() { - if !seen.insert(name.clone()) { - continue; - } - if let Some(dependencies) = self.loaders.get(&name) { - pending.extend(dependencies.iter().cloned()); - } - names.push(name); + let mut names: VisitOrder<&String> = VisitOrder::default(); + for name in dependencies { + let Ok(_) = names.insert_new(&name); } - names + let Ok(()) = breadth_first_reach(dependencies, None, &mut names, |name| { + Ok::<_, Infallible>(self.loaders.get(*name).into_iter().flatten()) + }); + names.order.into_iter().cloned().collect() } /// Source key for a loader, using the last source declaration for that loader. diff --git a/crates/sqlbuild-analysis/src/python_plan/main/upstream_closure.rs b/crates/sqlbuild-analysis/src/python_plan/main/upstream_closure.rs index e216ba75c9..2c0d9d8509 100644 --- a/crates/sqlbuild-analysis/src/python_plan/main/upstream_closure.rs +++ b/crates/sqlbuild-analysis/src/python_plan/main/upstream_closure.rs @@ -1,18 +1,22 @@ //! Every node upstream of one Python node, sorted. -use std::collections::{HashMap, HashSet, VecDeque}; +use std::collections::{HashMap, HashSet}; +use std::convert::Infallible; + +use sqlbuild_core::graph_walk::main::breadth_first_reach::breadth_first_reach; /// `sorted(transitive_closure(start=start, edges=upstream))`: `start` only when on a cycle. pub fn upstream_closure(start: &str, upstream: &HashMap>) -> Vec { let mut visited: HashSet<&str> = HashSet::new(); - let mut frontier: VecDeque<&str> = VecDeque::from([start]); - while let Some(current) = frontier.pop_front() { - for neighbor in upstream.get(current).into_iter().flatten() { - if visited.insert(neighbor) { - frontier.push_back(neighbor); - } - } - } + let Ok(()) = breadth_first_reach([start], None, &mut visited, |current| { + Ok::<_, Infallible>( + upstream + .get(*current) + .into_iter() + .flatten() + .map(String::as_str), + ) + }); let mut names: Vec = visited.into_iter().map(str::to_owned).collect(); names.sort(); names diff --git a/crates/sqlbuild-analysis/src/python_selection/_helpers/nodes.rs b/crates/sqlbuild-analysis/src/python_selection/_helpers/nodes.rs index e3d4e92112..34f8bf1c3a 100644 --- a/crates/sqlbuild-analysis/src/python_selection/_helpers/nodes.rs +++ b/crates/sqlbuild-analysis/src/python_selection/_helpers/nodes.rs @@ -1,7 +1,9 @@ //! Python's `resolve_python_node_selectors`: selector resolution over Python node names. -use std::collections::{BTreeSet, HashMap, HashSet, VecDeque}; +use std::collections::{BTreeSet, HashMap, HashSet}; +use std::convert::Infallible; +use sqlbuild_core::graph_walk::main::breadth_first_reach::breadth_first_reach; use sqlbuild_core::text::main::glob_match::glob_match; use crate::assembly::project::types::ObjectKey; @@ -89,19 +91,14 @@ impl<'graph> NodeIndex<'graph> { starts: impl IntoIterator, direction: EdgeDirection, ) -> Names { - let mut visited: HashSet<&str> = HashSet::new(); - let mut frontier: VecDeque<&str> = VecDeque::new(); - for start in starts { - frontier.push_back(start); - } - while let Some(current) = frontier.pop_front() { - for neighbor in self.edges(current, direction) { - if visited.insert(neighbor) { - frontier.push_back(neighbor); - } - } - } - visited.into_iter().map(str::to_owned).collect() + let mut visited: HashSet = HashSet::new(); + let Ok(()) = breadth_first_reach( + starts.into_iter().map(str::to_owned), + None, + &mut visited, + |current| Ok::<_, Infallible>(self.edges(current, direction).iter().cloned()), + ); + visited.into_iter().collect() } /// Whether `name` is a node of `kind`. diff --git a/crates/sqlbuild-analysis/src/python_selection/_helpers/unified.rs b/crates/sqlbuild-analysis/src/python_selection/_helpers/unified.rs index 8d91e471af..523963f25e 100644 --- a/crates/sqlbuild-analysis/src/python_selection/_helpers/unified.rs +++ b/crates/sqlbuild-analysis/src/python_selection/_helpers/unified.rs @@ -1,7 +1,9 @@ //! Python's unified selector resolution across SQL resources and Python nodes. -use std::collections::{BTreeSet, HashMap, HashSet, VecDeque}; +use std::collections::{BTreeSet, HashMap, HashSet}; +use std::convert::Infallible; +use sqlbuild_core::graph_walk::main::breadth_first_reach::breadth_first_reach; use sqlbuild_core::text::main::glob_match::glob_match; use crate::assembly::project::types::ObjectKey; @@ -192,14 +194,13 @@ fn sql_dependency_upstream(context: &SelectionContext<'_>, atoms: &Atoms) -> Ato return Atoms::new(); } let mut visited: HashSet<&ObjectKey> = starts.iter().copied().collect(); - let mut frontier: VecDeque<&ObjectKey> = starts.iter().copied().collect(); - while let Some(current) = frontier.pop_front() { - for neighbor in edge_keys(context.sql.graph, current, EdgeDirection::Upstream) { - if visited.insert(neighbor) { - frontier.push_back(neighbor); - } - } - } + let Ok(()) = breadth_first_reach(starts, None, &mut visited, |current| { + Ok::<_, Infallible>(edge_keys( + context.sql.graph, + current, + EdgeDirection::Upstream, + )) + }); let selected: Atoms = sql_atoms(visited.into_iter().cloned()); let loaders: Atoms = terminal_loader_atoms(context, &selected); selected.into_iter().chain(loaders).collect() diff --git a/crates/sqlbuild-analysis/src/semantic_checks/_helpers/recovery/type_recovery.rs b/crates/sqlbuild-analysis/src/semantic_checks/_helpers/recovery/type_recovery.rs index 1e065476f8..50df8ea5af 100644 --- a/crates/sqlbuild-analysis/src/semantic_checks/_helpers/recovery/type_recovery.rs +++ b/crates/sqlbuild-analysis/src/semantic_checks/_helpers/recovery/type_recovery.rs @@ -1,6 +1,9 @@ //! Python's `recover_output_types`: unknown types for outputs poisoned by located root errors. use std::collections::{HashMap, HashSet, VecDeque}; +use std::convert::Infallible; + +use sqlbuild_core::graph_walk::main::breadth_first_reach::breadth_first_reach; use crate::semantic_checks::_helpers::recovery::bindings::{BindingOwner, binding_positions}; use crate::semantic_checks::_helpers::recovery::poison::Poison; @@ -266,19 +269,18 @@ fn unchecked_uses( let mut counts: HashMap = HashMap::new(); for model in &request.models { for output in &model.lineage { - let mut pending: Vec = output + let roots: Vec = output .upstream .iter() .filter_map(|(resource, column)| poisoned.get(resource, column)) .collect(); - let mut visited: HashSet = HashSet::new(); - while let Some(root) = pending.pop() { - if !visited.insert(root) { - continue; - } - match redirected.get(&root) { - Some(targets) => pending.extend(targets.iter().copied()), - None => *counts.entry(root).or_default() += 1, + let mut visited: HashSet = roots.iter().copied().collect(); + let Ok(()) = breadth_first_reach(roots, None, &mut visited, |root| { + Ok::<_, Infallible>(redirected.get(root).into_iter().flatten().copied()) + }); + for root in visited { + if !redirected.contains_key(&root) { + *counts.entry(root).or_default() += 1; } } } diff --git a/crates/sqlbuild-analysis/src/sql_test_attachment/_helpers/attachment_checks.rs b/crates/sqlbuild-analysis/src/sql_test_attachment/_helpers/attachment_checks.rs index ce8589f794..9cf9817e02 100644 --- a/crates/sqlbuild-analysis/src/sql_test_attachment/_helpers/attachment_checks.rs +++ b/crates/sqlbuild-analysis/src/sql_test_attachment/_helpers/attachment_checks.rs @@ -1,6 +1,12 @@ //! `helper_ctes`: the helper CTEs a model test reads and the references it cannot resolve. -use std::collections::{HashMap, HashSet, VecDeque}; +use std::collections::{HashMap, HashSet}; +use std::convert::Infallible; + +use sqlbuild_core::graph_walk::main::breadth_first_path::breadth_first_path; +use sqlbuild_core::graph_walk::main::breadth_first_reach::breadth_first_reach; +use sqlbuild_core::graph_walk::models::VisitOrder; +use sqlbuild_core::graph_walk::types::NodeSet; use sqlbuild_sqltext::sql_references::main::extract_sql_references::extract_sql_references; use sqlbuild_sqltext::sql_references::models::{ReferenceExtraction, SqlReference}; @@ -39,26 +45,18 @@ impl CteGraph<'_> { /// `reachable_cte_keys`: the roots then what they read, breadth first, in visit order. fn reachable(&self, roots: &[String]) -> Vec { - let mut pending: VecDeque = roots + let known: Vec<&String> = roots .iter() .filter(|root| self.ctes.contains_key(*root)) - .cloned() .collect(); - let mut visited: Vec = Vec::new(); - for root in &pending { - if !visited.contains(root) { - visited.push(root.clone()); - } - } - while let Some(key) = pending.pop_front() { - for dependency in self.reads.get(&key).into_iter().flatten() { - if !visited.contains(dependency) { - visited.push(dependency.clone()); - pending.push_back(dependency.clone()); - } - } + let mut visited: VisitOrder<&String> = VisitOrder::default(); + for root in &known { + let Ok(_) = visited.insert_new(root); } - visited + let Ok(()) = breadth_first_reach(known, None, &mut visited, |key| { + Ok::<_, Infallible>(self.reads.get(*key).into_iter().flatten()) + }); + visited.order.into_iter().cloned().collect() } /// `_read_helper_keys`: reachable authored CTEs that are not mocks. @@ -304,22 +302,11 @@ pub(crate) fn unresolvable_test_references( fn first_cycle(graph: &CteGraph<'_>, keys: &[String]) -> Option> { let allowed: HashSet<&String> = keys.iter().collect(); for origin in keys { - let mut pending: VecDeque> = VecDeque::from([vec![origin.clone()]]); - let mut visited: HashSet = HashSet::new(); - while let Some(path) = pending.pop_front() { - let last: &String = path.last()?; - for dependency in graph.parsed_reads.get(last).into_iter().flatten() { - if dependency == origin { - let mut closed: Vec = path.clone(); - closed.push(origin.clone()); - return Some(closed); - } - if allowed.contains(dependency) && visited.insert(dependency.clone()) { - let mut next: Vec = path.clone(); - next.push(dependency.clone()); - pending.push_back(next); - } - } + let reads = |last: &&String| graph.parsed_reads.get(*last).into_iter().flatten(); + let closes = |dependency: &&String| *dependency == origin; + let enters = |dependency: &&String| allowed.contains(*dependency); + if let Some(path) = breadth_first_path(origin, (reads, closes, enters)) { + return Some(path.into_iter().cloned().collect()); } } None diff --git a/crates/sqlbuild-cache/src/hosted_shapes/constants.rs b/crates/sqlbuild-cache/src/hosted_shapes/constants.rs index 8f30546304..9bd44bb448 100644 --- a/crates/sqlbuild-cache/src/hosted_shapes/constants.rs +++ b/crates/sqlbuild-cache/src/hosted_shapes/constants.rs @@ -1,7 +1,5 @@ //! The Python shape names of the natively hosted compiler shapes. -/// The extension module hosting the shapes. -pub const NATIVE_MODULE: &str = "sqlbuild._native"; /// The Python model type a natively hosted compiler shape is captured as. pub const HOSTED_SHAPE_TYPES: [(&str, &str); 491] = [ ( diff --git a/crates/sqlbuild-cache/src/hosted_shapes/errors.rs b/crates/sqlbuild-cache/src/hosted_shapes/errors.rs deleted file mode 100644 index 0f1f6ce65b..0000000000 --- a/crates/sqlbuild-cache/src/hosted_shapes/errors.rs +++ /dev/null @@ -1,5 +0,0 @@ -//! Why a Python shape name could not be resolved to one native class. - -/// More than one natively hosted class claims the same Python shape name. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct AmbiguousShapeName; diff --git a/crates/sqlbuild-cache/src/hosted_shapes/main/captured_shape_name.rs b/crates/sqlbuild-cache/src/hosted_shapes/main/captured_shape_name.rs new file mode 100644 index 0000000000..8c2cd7fea5 --- /dev/null +++ b/crates/sqlbuild-cache/src/hosted_shapes/main/captured_shape_name.rs @@ -0,0 +1,11 @@ +//! The Python shape name a natively hosted class is captured as. + +use crate::hosted_shapes::constants::HOSTED_SHAPE_TYPES; + +/// The `module:qualname` the native class `native` is captured as, when it hosts a Python shape. +pub fn captured_shape_name(native: &str) -> Option<&'static str> { + HOSTED_SHAPE_TYPES + .iter() + .find(|(hosted, _)| *hosted == native) + .map(|(_, python)| *python) +} diff --git a/crates/sqlbuild-cache/src/hosted_shapes/main/hosted_native_name.rs b/crates/sqlbuild-cache/src/hosted_shapes/main/hosted_native_name.rs deleted file mode 100644 index 62473e0934..0000000000 --- a/crates/sqlbuild-cache/src/hosted_shapes/main/hosted_native_name.rs +++ /dev/null @@ -1,28 +0,0 @@ -//! The native class hosting one Python shape, found by its module and class name. - -use crate::hosted_shapes::constants::{HOSTED_SHAPE_TYPES, NATIVE_MODULE}; -use crate::hosted_shapes::errors::AmbiguousShapeName; - -/// The native class name hosting `module:class`, or `None` when the shape is still Python. -pub fn hosted_native_name( - module: &str, - class: &str, -) -> Result, AmbiguousShapeName> { - let mut found: Option<&'static str> = None; - for (native, python) in HOSTED_SHAPE_TYPES { - if python.split_once(':') != Some((module, class)) { - continue; - } - let Some(native_name) = native - .strip_prefix(NATIVE_MODULE) - .and_then(|rest| rest.strip_prefix(':')) - else { - continue; - }; - if found.is_some_and(|existing| existing != native_name) { - return Err(AmbiguousShapeName); - } - found = Some(native_name); - } - Ok(found) -} diff --git a/crates/sqlbuild-cache/src/hosted_shapes/main/mod.rs b/crates/sqlbuild-cache/src/hosted_shapes/main/mod.rs index 99b510965b..c4cfc62bbd 100644 --- a/crates/sqlbuild-cache/src/hosted_shapes/main/mod.rs +++ b/crates/sqlbuild-cache/src/hosted_shapes/main/mod.rs @@ -1 +1 @@ -pub mod hosted_native_name; +pub mod captured_shape_name; diff --git a/crates/sqlbuild-cache/src/hosted_shapes/mod.rs b/crates/sqlbuild-cache/src/hosted_shapes/mod.rs index 45be7c1b09..3e004db88f 100644 --- a/crates/sqlbuild-cache/src/hosted_shapes/mod.rs +++ b/crates/sqlbuild-cache/src/hosted_shapes/mod.rs @@ -1,7 +1,6 @@ -//! Natively hosted compiler shapes and the Python names they are captured and resolved as. +//! Natively hosted compiler shapes and the Python names they are captured as. pub mod constants; -pub mod errors; pub mod main; #[cfg(test)] diff --git a/crates/sqlbuild-cache/src/hosted_shapes/tests/test_hosted_shape_types.rs b/crates/sqlbuild-cache/src/hosted_shapes/tests/test_hosted_shape_types.rs index 8070b1c832..d63720c1cb 100644 --- a/crates/sqlbuild-cache/src/hosted_shapes/tests/test_hosted_shape_types.rs +++ b/crates/sqlbuild-cache/src/hosted_shapes/tests/test_hosted_shape_types.rs @@ -1,7 +1,7 @@ use crate::hosted_shapes::constants::HOSTED_SHAPE_TYPES; -use crate::hosted_shapes::main::hosted_native_name::hosted_native_name; +use crate::hosted_shapes::main::captured_shape_name::captured_shape_name; use crate::hosted_shapes::tests::helpers::duplicates; -use crate::hosted_shapes::tests::test_types::{HostedKeyTestCase, NativeNameTestCase}; +use crate::hosted_shapes::tests::test_types::{CapturedNameTestCase, HostedKeyTestCase}; #[test] fn given_hosted_shape_table_when_keyed_then_every_native_and_python_name_is_unique() { @@ -34,28 +34,23 @@ fn given_hosted_shape_table_when_keyed_then_every_native_and_python_name_is_uniq } #[test] -fn given_python_shape_when_resolving_then_only_its_own_module_selects_the_native_class() { +fn given_native_class_when_naming_its_capture_then_only_hosted_shapes_keep_a_python_name() { let test_cases = [ - NativeNameTestCase { - description: "a scope severity resolves to the scope enum", - shape: ("sqlbuild.compiler.scopes.types", "DiagnosticSeverity"), - expected_native: Some("ScopeDiagnosticSeverity"), + CapturedNameTestCase { + description: "a hosted planner record keeps its Python name", + native: "sqlbuild._native:PlanOutput", + expected_python: Some("sqlbuild.compiler.planner.models:PlanOutput"), }, - NativeNameTestCase { - description: "a shape name from another module is not hosted", - shape: ("sqlbuild.compiler.compile.models", "DiagnosticSeverity"), - expected_native: None, - }, - NativeNameTestCase { - description: "a hosted planner record resolves by its own name", - shape: ("sqlbuild.compiler.planner.models", "PlanOutput"), - expected_native: Some("PlanOutput"), + CapturedNameTestCase { + description: "a native class hosting no shape has no Python name", + native: "sqlbuild._native:NoSuchShape", + expected_python: None, }, ]; for test_case in test_cases { assert_eq!( - hosted_native_name(test_case.shape.0, test_case.shape.1), - Ok(test_case.expected_native), + captured_shape_name(test_case.native), + test_case.expected_python, "{}", test_case.description ); diff --git a/crates/sqlbuild-cache/src/hosted_shapes/tests/test_types.rs b/crates/sqlbuild-cache/src/hosted_shapes/tests/test_types.rs index 68ad02324d..a658846a2f 100644 --- a/crates/sqlbuild-cache/src/hosted_shapes/tests/test_types.rs +++ b/crates/sqlbuild-cache/src/hosted_shapes/tests/test_types.rs @@ -4,8 +4,8 @@ pub(crate) struct HostedKeyTestCase { pub(crate) expected_duplicates: Vec<&'static str>, } -pub(crate) struct NativeNameTestCase { +pub(crate) struct CapturedNameTestCase { pub(crate) description: &'static str, - pub(crate) shape: (&'static str, &'static str), - pub(crate) expected_native: Option<&'static str>, + pub(crate) native: &'static str, + pub(crate) expected_python: Option<&'static str>, } diff --git a/crates/sqlbuild-cli/src/dispatch/_helpers/routes.rs b/crates/sqlbuild-cli/src/dispatch/_helpers/routes.rs index b801524ca8..181b38e845 100644 --- a/crates/sqlbuild-cli/src/dispatch/_helpers/routes.rs +++ b/crates/sqlbuild-cli/src/dispatch/_helpers/routes.rs @@ -2,10 +2,10 @@ use crate::dispatch::models::{CallShape, ClassRef, Field, Route, Source}; -const DBT_IMPLEMENTATION: (&str, &str) = ("sqlbuild._native", "run_dbt_command"); +const DBT_IMPLEMENTATION: &str = "run_dbt_command"; -pub(crate) const fn class(module: &'static str, name: &'static str) -> ClassRef { - ClassRef { module, name } +pub(crate) const fn class(name: &'static str) -> ClassRef { + ClassRef { name } } pub(crate) const fn field(name: &'static str, source: Source) -> Field { @@ -26,7 +26,7 @@ pub(crate) const fn request(class_ref: ClassRef, fields: &'static [Field]) -> Fi pub(crate) const fn routed( slot: &'static str, - implementation: (&'static str, &'static str), + implementation: &'static str, arguments: &'static [Field], shape: CallShape, ) -> Route { diff --git a/crates/sqlbuild-cli/src/dispatch/constants.rs b/crates/sqlbuild-cli/src/dispatch/constants.rs index e375fd4a63..0e28af1fe5 100644 --- a/crates/sqlbuild-cli/src/dispatch/constants.rs +++ b/crates/sqlbuild-cli/src/dispatch/constants.rs @@ -11,10 +11,6 @@ pub(crate) const DBT_COMMAND_NAME: &str = "dbt"; pub(crate) const DBT_INIT_SUBCOMMAND: &str = "init"; pub(crate) const DIFF_COMMAND_NAME: &str = "diff"; -const NATIVE_RECORDS: &str = "sqlbuild._native"; -const ENTRY_MODELS: &str = "sqlbuild._native"; -const PLANNER_MODELS: &str = "sqlbuild._native"; - const PROJECT_DIR: Field = field("project_dir", Source::ProjectDir); const SELECT: Field = field("select", Source::Select); const EXCLUDE: Field = field("exclude", Source::TupleOf("exclude")); @@ -35,7 +31,7 @@ const CURSOR_FIELDS: [Field; 6] = [ ]; const CURSOR_OVERRIDES: Field = field( "cursor_overrides", - Source::Nested(class(PLANNER_MODELS, "CursorOverrides"), &CURSOR_FIELDS), + Source::Nested(class("CursorOverrides"), &CURSOR_FIELDS), ); const SNAPSHOT_LIMIT_FIELDS: [Field; 5] = [ @@ -53,10 +49,7 @@ const SNAPSHOT_LIMIT_FIELDS: [Field; 5] = [ ]; const SNAPSHOT_LIMITS: Field = field( "limit_inputs", - Source::Nested( - class(NATIVE_RECORDS, "ScenarioSnapshotLimitInputs"), - &SNAPSHOT_LIMIT_FIELDS, - ), + Source::Nested(class("ScenarioSnapshotLimitInputs"), &SNAPSHOT_LIMIT_FIELDS), ); /// A handler receiving one request positionally. @@ -94,13 +87,10 @@ const SCOPE_FIELDS: [Field; 22] = [ same("verbose"), NO_COLOR, ]; -const SCOPE_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "ScopeCommandRequest"), - &SCOPE_FIELDS, -)]; +const SCOPE_ARGUMENTS: [Field; 1] = [request(class("ScopeCommandRequest"), &SCOPE_FIELDS)]; pub(crate) const SCOPE_ROUTE: Route = routed( "run_scope", - ("sqlbuild._native", "run_scope_command"), + "run_scope_command", &SCOPE_ARGUMENTS, REQUEST_KEYWORD_SHAPE, ); @@ -108,7 +98,7 @@ pub(crate) const SCOPE_ROUTE: Route = routed( const DAG_ARGUMENTS: [Field; 4] = [PROJECT_DIR, NO_SQL_VALIDATION, JSON_OUTPUT, CLI_VARS]; pub(crate) const DAG_ROUTE: Route = routed( "run_dag", - ("sqlbuild._native", "run_dag_command"), + "run_dag_command", &DAG_ARGUMENTS, CallShape { slot_positional: 0, @@ -137,13 +127,10 @@ const PLAN_FIELDS: [Field; 19] = [ same("max_microbatches"), same("selection_diagnostics"), ]; -const PLAN_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "PlanCommandRequest"), - &PLAN_FIELDS, -)]; +const PLAN_ARGUMENTS: [Field; 1] = [request(class("PlanCommandRequest"), &PLAN_FIELDS)]; pub(crate) const PLAN_ROUTE: Route = routed( "run_plan", - ("sqlbuild._native", "run_plan_command"), + "run_plan_command", &PLAN_ARGUMENTS, REQUEST_KEYWORD_SHAPE, ); @@ -184,13 +171,10 @@ const BUILD_FIELDS: [Field; 34] = [ same("max_microbatches"), same("selection_diagnostics"), ]; -const BUILD_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "BuildCommandRequest"), - &BUILD_FIELDS, -)]; +const BUILD_ARGUMENTS: [Field; 1] = [request(class("BuildCommandRequest"), &BUILD_FIELDS)]; pub(crate) const BUILD_ROUTE: Route = routed( "run_build", - ("sqlbuild._native", "run_build_command"), + "run_build_command", &BUILD_ARGUMENTS, REQUEST_KEYWORD_SHAPE, ); @@ -209,13 +193,11 @@ const FRESHNESS_FIELDS: [Field; 12] = [ attr("compare_state", "state"), same("fail_on_stale"), ]; -const FRESHNESS_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "FreshnessCommandRequest"), - &FRESHNESS_FIELDS, -)]; +const FRESHNESS_ARGUMENTS: [Field; 1] = + [request(class("FreshnessCommandRequest"), &FRESHNESS_FIELDS)]; pub(crate) const FRESHNESS_ROUTE: Route = routed( "run_freshness", - ("sqlbuild._native", "run_freshness_command"), + "run_freshness_command", &FRESHNESS_ARGUMENTS, REQUEST_KEYWORD_SHAPE, ); @@ -234,13 +216,10 @@ const TEST_FIELDS: [Field; 12] = [ attr("case_name", "case"), same("inspect"), ]; -const TEST_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "TestCommandRequest"), - &TEST_FIELDS, -)]; +const TEST_ARGUMENTS: [Field; 1] = [request(class("TestCommandRequest"), &TEST_FIELDS)]; pub(crate) const TEST_ROUTE: Route = routed( "run_test", - ("sqlbuild._native", "run_test_command"), + "run_test_command", &TEST_ARGUMENTS, REQUEST_KEYWORD_SHAPE, ); @@ -256,13 +235,10 @@ const CHECK_FIELDS: [Field; 9] = [ JSON_OUTPUT, JSON_OUTPUT_PATH, ]; -const CHECK_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "CheckCommandRequest"), - &CHECK_FIELDS, -)]; +const CHECK_ARGUMENTS: [Field; 1] = [request(class("CheckCommandRequest"), &CHECK_FIELDS)]; pub(crate) const CHECK_ROUTE: Route = routed( "run_check", - ("sqlbuild._native", "run_check_command"), + "run_check_command", &CHECK_ARGUMENTS, REQUEST_KEYWORD_SHAPE, ); @@ -280,13 +256,10 @@ const AUDIT_FIELDS: [Field; 11] = [ JSON_OUTPUT, JSON_OUTPUT_PATH, ]; -const AUDIT_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "AuditCommandRequest"), - &AUDIT_FIELDS, -)]; +const AUDIT_ARGUMENTS: [Field; 1] = [request(class("AuditCommandRequest"), &AUDIT_FIELDS)]; pub(crate) const AUDIT_ROUTE: Route = routed( "run_audit", - ("sqlbuild._native", "run_audit_command"), + "run_audit_command", &AUDIT_ARGUMENTS, REQUEST_KEYWORD_SHAPE, ); @@ -304,13 +277,10 @@ const LOAD_FIELDS: [Field; 11] = [ JSON_OUTPUT, JSON_OUTPUT_PATH, ]; -const LOAD_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "LoadCommandRequest"), - &LOAD_FIELDS, -)]; +const LOAD_ARGUMENTS: [Field; 1] = [request(class("LoadCommandRequest"), &LOAD_FIELDS)]; pub(crate) const LOAD_ROUTE: Route = routed( "run_load", - ("sqlbuild._native", "run_load_command"), + "run_load_command", &LOAD_ARGUMENTS, REQUEST_KEYWORD_SHAPE, ); @@ -326,13 +296,10 @@ const SEED_FIELDS: [Field; 9] = [ JSON_OUTPUT, JSON_OUTPUT_PATH, ]; -const SEED_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "SeedCommandRequest"), - &SEED_FIELDS, -)]; +const SEED_ARGUMENTS: [Field; 1] = [request(class("SeedCommandRequest"), &SEED_FIELDS)]; pub(crate) const SEED_ROUTE: Route = routed( "run_seed", - ("sqlbuild._native", "run_seed_command"), + "run_seed_command", &SEED_ARGUMENTS, REQUEST_KEYWORD_SHAPE, ); @@ -348,21 +315,15 @@ const LINEAGE_FIELDS: [Field; 11] = [ EXCLUDE, field( "lineage_mode", - Source::Member( - class("sqlbuild._native", "ColumnLineageMode"), - "lineage_mode", - ), + Source::Member(class("ColumnLineageMode"), "lineage_mode"), ), attr("include_uses", "lineage_include_uses"), CLI_VARS, ]; -const LINEAGE_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "LineageCommandRequest"), - &LINEAGE_FIELDS, -)]; +const LINEAGE_ARGUMENTS: [Field; 1] = [request(class("LineageCommandRequest"), &LINEAGE_FIELDS)]; pub(crate) const LINEAGE_ROUTE: Route = routed( "run_lineage", - ("sqlbuild._native", "run_lineage_command"), + "run_lineage_command", &LINEAGE_ARGUMENTS, REQUEST_KEYWORD_SHAPE, ); @@ -381,13 +342,10 @@ const CLONE_FIELDS: [Field; 12] = [ JSON_OUTPUT_PATH, attr("event_output_path", "event_output"), ]; -const CLONE_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "CloneCommandRequest"), - &CLONE_FIELDS, -)]; +const CLONE_ARGUMENTS: [Field; 1] = [request(class("CloneCommandRequest"), &CLONE_FIELDS)]; pub(crate) const CLONE_ROUTE: Route = routed( "run_clone", - ("sqlbuild._native", "run_clone_command"), + "run_clone_command", &CLONE_ARGUMENTS, REQUEST_KEYWORD_SHAPE, ); @@ -432,13 +390,10 @@ const DIFF_FIELDS: [Field; 35] = [ attr("suppress_example_values", "no_example_values"), same("full_example_values"), ]; -const DIFF_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "DiffCommandRequest"), - &DIFF_FIELDS, -)]; +const DIFF_ARGUMENTS: [Field; 1] = [request(class("DiffCommandRequest"), &DIFF_FIELDS)]; pub(crate) const DIFF_ROUTE: Route = routed( "run_diff", - ("sqlbuild._native", "run_diff_command"), + "run_diff_command", &DIFF_ARGUMENTS, REQUEST_KEYWORD_SHAPE, ); @@ -453,7 +408,7 @@ const QUERY_ARGUMENTS: [Field; 6] = [ ]; pub(crate) const QUERY_ROUTE: Route = routed( "run_query", - ("sqlbuild._native", "run_query_command"), + "run_query_command", &QUERY_ARGUMENTS, CallShape { slot_positional: 0, @@ -474,13 +429,10 @@ const COST_FIELDS: [Field; 11] = [ JSON_OUTPUT, JSON_OUTPUT_PATH, ]; -const COST_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "CostCommandRequest"), - &COST_FIELDS, -)]; +const COST_ARGUMENTS: [Field; 1] = [request(class("CostCommandRequest"), &COST_FIELDS)]; pub(crate) const COST_ROUTE: Route = routed( "run_cost", - ("sqlbuild._native", "run_cost_command"), + "run_cost_command", &COST_ARGUMENTS, REQUEST_SHAPE, ); @@ -494,7 +446,7 @@ const DEBUG_ARGUMENTS: [Field; 5] = [ ]; pub(crate) const DEBUG_ROUTE: Route = routed( "run_debug", - ("sqlbuild._native", "run_debug_command"), + "run_debug_command", &DEBUG_ARGUMENTS, CallShape { slot_positional: 0, @@ -515,13 +467,10 @@ const JANITOR_FIELDS: [Field; 8] = [ SELECTED_TARGET, same("as_target"), ]; -const JANITOR_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "JanitorCommandRequest"), - &JANITOR_FIELDS, -)]; +const JANITOR_ARGUMENTS: [Field; 1] = [request(class("JanitorCommandRequest"), &JANITOR_FIELDS)]; pub(crate) const JANITOR_ROUTE: Route = routed( "run_janitor", - ("sqlbuild._native", "run_janitor_command"), + "run_janitor_command", &JANITOR_ARGUMENTS, REQUEST_KEYWORD_SHAPE, ); @@ -537,15 +486,12 @@ const REFACTOR_FIELDS: [Field; 9] = [ JSON_OUTPUT, NO_COLOR, ]; -const REFACTOR_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "RefactorCommandRequest"), - &REFACTOR_FIELDS, -)]; +const REFACTOR_ARGUMENTS: [Field; 1] = [request(class("RefactorCommandRequest"), &REFACTOR_FIELDS)]; pub(crate) const REFACTOR_ROUTE: Route = Route { unavailable_code: Some("C950"), ..routed( "run_refactor", - ("sqlbuild._native", "run_refactor_command"), + "run_refactor_command", &REFACTOR_ARGUMENTS, REQUEST_KEYWORD_SHAPE, ) @@ -563,15 +509,12 @@ const CONTRACT_FIELDS: [Field; 10] = [ NO_COLOR, CLI_VARS, ]; -const CONTRACT_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "ContractCommandRequest"), - &CONTRACT_FIELDS, -)]; +const CONTRACT_ARGUMENTS: [Field; 1] = [request(class("ContractCommandRequest"), &CONTRACT_FIELDS)]; pub(crate) const CONTRACT_ROUTE: Route = Route { unavailable_code: Some("C470"), ..routed( "run_contract", - ("sqlbuild._native", "run_contract_command"), + "run_contract_command", &CONTRACT_ARGUMENTS, REQUEST_KEYWORD_SHAPE, ) @@ -598,27 +541,21 @@ const COMPILE_FIELDS: [Field; 15] = [ NO_COLOR, field( "lineage_mode", - Source::Member( - class("sqlbuild._native", "CompileLineageMode"), - "compile_lineage_mode", - ), + Source::Member(class("CompileLineageMode"), "compile_lineage_mode"), ), SELECT, EXCLUDE, CLI_VARS, field( "profile_flags", - Source::Nested(class(ENTRY_MODELS, "CompileProfileFlags"), &PROFILE_FIELDS), + Source::Nested(class("CompileProfileFlags"), &PROFILE_FIELDS), ), same("debug"), ]; -const COMPILE_ARGUMENTS: [Field; 1] = [request( - class(ENTRY_MODELS, "CompileCommandRequest"), - &COMPILE_FIELDS, -)]; +const COMPILE_ARGUMENTS: [Field; 1] = [request(class("CompileCommandRequest"), &COMPILE_FIELDS)]; pub(crate) const COMPILE_ROUTE: Route = routed( "run_compile", - ("sqlbuild._native", "run_compile"), + "run_compile", &COMPILE_ARGUMENTS, REQUEST_SHAPE, ); @@ -626,7 +563,7 @@ pub(crate) const COMPILE_ROUTE: Route = routed( const INIT_ARGUMENTS: [Field; 1] = [PROJECT_DIR]; pub(crate) const INIT_ROUTE: Route = routed( "run_init", - ("sqlbuild._native", "run_init_command"), + "run_init_command", &INIT_ARGUMENTS, REQUEST_SHAPE, ); @@ -637,12 +574,12 @@ const PLAYGROUND_FIELDS: [Field; 3] = [ attr("template", "playground_template"), ]; const PLAYGROUND_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "PlaygroundCommandRequest"), + class("PlaygroundCommandRequest"), &PLAYGROUND_FIELDS, )]; pub(crate) const PLAYGROUND_ROUTE: Route = routed( "run_playground", - ("sqlbuild._native", "run_playground_command"), + "run_playground_command", &PLAYGROUND_ARGUMENTS, REQUEST_SHAPE, ); @@ -655,7 +592,7 @@ const SKILLS_ARGUMENTS: [Field; 4] = [ ]; pub(crate) const SKILLS_ROUTE: Route = routed( "run_skills_update", - ("sqlbuild._native", "run_skills_command"), + "run_skills_command", &SKILLS_ARGUMENTS, CallShape { slot_positional: 0, @@ -676,7 +613,7 @@ const FORMAT_ARGUMENTS: [Field; 9] = [ ]; pub(crate) const FORMAT_ROUTE: Route = routed( "run_format", - ("sqlbuild._native", "run_format_command"), + "run_format_command", &FORMAT_ARGUMENTS, CallShape { slot_positional: 1, @@ -693,13 +630,10 @@ const RULES_FIELDS: [Field; 7] = [ field("select", Source::TupleOf("select")), EXCLUDE, ]; -const RULES_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "RulesCommandRequest"), - &RULES_FIELDS, -)]; +const RULES_ARGUMENTS: [Field; 1] = [request(class("RulesCommandRequest"), &RULES_FIELDS)]; pub(crate) const RULES_ROUTE: Route = routed( "run_rules", - ("sqlbuild._native", "run_rules_command"), + "run_rules_command", &RULES_ARGUMENTS, REQUEST_KEYWORD_SHAPE, ); @@ -715,13 +649,10 @@ const DBT_INIT_FIELDS: [Field; 9] = [ same("overwrite"), same("skip_dbt_debug"), ]; -const DBT_INIT_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "DbtInitCommandRequest"), - &DBT_INIT_FIELDS, -)]; +const DBT_INIT_ARGUMENTS: [Field; 1] = [request(class("DbtInitCommandRequest"), &DBT_INIT_FIELDS)]; pub(crate) const DBT_INIT_ROUTE: Route = routed( "run_dbt_init", - ("sqlbuild._native", "run_dbt_init_command"), + "run_dbt_init_command", &DBT_INIT_ARGUMENTS, REQUEST_SHAPE, ); @@ -731,7 +662,7 @@ const DBT_ARGUMENTS: [Field; 3] = [ field("args", Source::TupleOf("dbt_args")), NO_COLOR, ]; -const DBT_COMMAND: ClassRef = class("sqlbuild._native", "DbtInteropCommand"); +const DBT_COMMAND: ClassRef = class("DbtInteropCommand"); pub(crate) const DBT_PLAN_ROUTE: Route = dbt_route("run_dbt_plan", "plan", &DBT_ARGUMENTS, DBT_COMMAND); @@ -769,12 +700,12 @@ const SCENARIO_TEST_FIELDS: [Field; 15] = [ JSON_OUTPUT_PATH, ]; const SCENARIO_TEST_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "ScenarioTestCommandRequest"), + class("ScenarioTestCommandRequest"), &SCENARIO_TEST_FIELDS, )]; pub(crate) const SCENARIO_TEST_ROUTE: Route = routed( "run_scenario", - ("sqlbuild._native", "run_scenario_test_command"), + "run_scenario_test_command", &SCENARIO_TEST_ARGUMENTS, REQUEST_KEYWORD_SHAPE, ); @@ -790,12 +721,12 @@ const SCENARIO_CAPTURE_FIELDS: [Field; 8] = [ SNAPSHOT_LIMITS, ]; const SCENARIO_CAPTURE_ARGUMENTS: [Field; 1] = [request( - class(NATIVE_RECORDS, "ScenarioCaptureCommandRequest"), + class("ScenarioCaptureCommandRequest"), &SCENARIO_CAPTURE_FIELDS, )]; pub(crate) const SCENARIO_CAPTURE_ROUTE: Route = routed( "run_scenario_capture", - ("sqlbuild._native", "run_scenario_capture_command"), + "run_scenario_capture_command", &SCENARIO_CAPTURE_ARGUMENTS, REQUEST_KEYWORD_SHAPE, ); diff --git a/crates/sqlbuild-cli/src/dispatch/models.rs b/crates/sqlbuild-cli/src/dispatch/models.rs index 87230c9bf4..9e22a922ce 100644 --- a/crates/sqlbuild-cli/src/dispatch/models.rs +++ b/crates/sqlbuild-cli/src/dispatch/models.rs @@ -1,9 +1,8 @@ //! How one parsed `sqb` invocation becomes a call of its Python command handler. -/// A Python class by module path and name. +/// A class `sqlbuild._native` registers, by name. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct ClassRef { - pub module: &'static str, pub name: &'static str, } @@ -74,8 +73,8 @@ pub struct CallShape { pub struct Route { /// The `CliEntrypointHandlers` attribute tests may inject. pub slot: &'static str, - /// The command implementation: module path and function name. - pub implementation: (&'static str, &'static str), + /// The name of the native function implementing the command. + pub implementation: &'static str, pub arguments: &'static [Field], pub shape: CallShape, /// A keyword the implementation alone receives, such as the dbt command. diff --git a/crates/sqlbuild-core/src/dagster_invocation/_helpers/graph.rs b/crates/sqlbuild-core/src/dagster_invocation/_helpers/graph.rs index 62602ba23f..d23aa00acd 100644 --- a/crates/sqlbuild-core/src/dagster_invocation/_helpers/graph.rs +++ b/crates/sqlbuild-core/src/dagster_invocation/_helpers/graph.rs @@ -1,6 +1,6 @@ //! Edge sets of a DAG artifact keyed by node position. -use std::collections::{BTreeSet, HashMap, VecDeque}; +use std::collections::{BTreeSet, HashMap}; /// Upstream positions and ordered, distinct downstream positions of each node. pub(crate) struct EdgeSets { @@ -58,15 +58,3 @@ pub(crate) fn id_positions(ids: &[String]) -> HashMap<&str, usize> { .map(|(position, id)| (id.as_str(), position)) .collect() } - -/// Nodes without upstream nodes, in node order. -pub(crate) fn initially_ready( - unique: &[usize], - incoming: &HashMap>, -) -> VecDeque { - unique - .iter() - .copied() - .filter(|position| incoming.get(position).is_none_or(BTreeSet::is_empty)) - .collect() -} diff --git a/crates/sqlbuild-core/src/dagster_invocation/main/topological_order.rs b/crates/sqlbuild-core/src/dagster_invocation/main/topological_order.rs index 7b8e5b12fd..14e5b3a1db 100644 --- a/crates/sqlbuild-core/src/dagster_invocation/main/topological_order.rs +++ b/crates/sqlbuild-core/src/dagster_invocation/main/topological_order.rs @@ -1,31 +1,26 @@ //! DAG node order for emitting Dagster events. -use std::collections::{BTreeSet, HashMap, VecDeque}; +use std::collections::{HashMap, VecDeque}; use crate::dagster_invocation::_helpers::graph::{ - EdgeSets, edge_sets, id_positions, initially_ready, unique_positions, + EdgeSets, edge_sets, id_positions, unique_positions, }; +use crate::graph_walk::main::kahn_order::kahn_order; /// Node positions in Kahn order over known edges, or `None` when a cycle leaves nodes out. #[must_use] pub fn topological_order(ids: &[String], edges: &[(String, String)]) -> Option> { let positions: HashMap<&str, usize> = id_positions(ids); let unique: Vec = unique_positions(ids, &positions); - let EdgeSets { - mut incoming, - outgoing, - } = edge_sets(&unique, (&positions, edges)); - let mut ready: VecDeque = initially_ready(&unique, &incoming); - let mut ordered: Vec = Vec::new(); - while let Some(position) = ready.pop_front() { - ordered.push(position); - for downstream in outgoing.get(&position).cloned().unwrap_or_default() { - let waiting: &mut BTreeSet = incoming.entry(downstream).or_default(); - waiting.remove(&position); - if waiting.is_empty() { - ready.push_back(downstream); - } - } - } + let EdgeSets { incoming, outgoing } = edge_sets(&unique, (&positions, edges)); + let remaining: HashMap = incoming + .iter() + .map(|(position, upstream)| (*position, upstream.len())) + .collect(); + let ordered: Vec = kahn_order( + (unique.iter().copied(), remaining), + VecDeque::new(), + |position| outgoing.get(position).cloned().unwrap_or_default(), + ); (ordered.len() == ids.len()).then_some(ordered) } diff --git a/crates/sqlbuild-core/src/dbt_graph/_helpers/closure.rs b/crates/sqlbuild-core/src/dbt_graph/_helpers/closure.rs deleted file mode 100644 index f51d0a2dc1..0000000000 --- a/crates/sqlbuild-core/src/dbt_graph/_helpers/closure.rs +++ /dev/null @@ -1,50 +0,0 @@ -//! Breadth-first reachability and path nodes over combined graph edges. - -use std::collections::{HashSet, VecDeque}; - -use crate::dbt_graph::models::{DbtGraphEdges, DbtGraphKey}; - -/// Every key reachable from `start`; `start` itself only through a cycle. -pub(crate) fn reachable(start: &DbtGraphKey, edges: &DbtGraphEdges) -> HashSet { - let mut visited: HashSet = HashSet::new(); - let mut frontier: VecDeque<&DbtGraphKey> = VecDeque::from([start]); - while let Some(current) = frontier.pop_front() { - for neighbor in edges.targets.get(current).into_iter().flatten() { - if visited.insert(neighbor.clone()) { - frontier.push_back(neighbor); - } - } - } - visited -} - -/// Keys on directed paths from `start` to `end`, or `None` when `end` is not downstream. -pub(crate) fn path_nodes( - (start, end): (&DbtGraphKey, &DbtGraphKey), - downstream: &DbtGraphEdges, - upstream: &DbtGraphEdges, -) -> Option> { - let reachable_keys: HashSet = reachable(start, downstream); - if !reachable_keys.contains(end) { - return None; - } - let mut upstream_from_end: HashSet = HashSet::new(); - let mut stack: Vec<&DbtGraphKey> = vec![end]; - while let Some(current) = stack.pop() { - if !upstream_from_end.insert(current.clone()) { - continue; - } - for parent in upstream.targets.get(current).into_iter().flatten() { - if reachable_keys.contains(parent) || parent == start { - stack.push(parent); - } - } - } - let mut nodes: HashSet = reachable_keys - .intersection(&upstream_from_end) - .cloned() - .collect(); - nodes.insert(start.clone()); - nodes.insert(end.clone()); - Some(nodes) -} diff --git a/crates/sqlbuild-core/src/dbt_graph/_helpers/mod.rs b/crates/sqlbuild-core/src/dbt_graph/_helpers/mod.rs index ced417d413..213c16d876 100644 --- a/crates/sqlbuild-core/src/dbt_graph/_helpers/mod.rs +++ b/crates/sqlbuild-core/src/dbt_graph/_helpers/mod.rs @@ -1,4 +1,3 @@ -pub(crate) mod closure; pub(crate) mod edges; pub(crate) mod endpoints; pub(crate) mod selection; diff --git a/crates/sqlbuild-core/src/dbt_graph/_helpers/selection.rs b/crates/sqlbuild-core/src/dbt_graph/_helpers/selection.rs index a4a4703bab..caf87c51e8 100644 --- a/crates/sqlbuild-core/src/dbt_graph/_helpers/selection.rs +++ b/crates/sqlbuild-core/src/dbt_graph/_helpers/selection.rs @@ -1,8 +1,8 @@ //! One selector term at a time: path selectors, SQLBuild selectors and dbt anchors. use std::collections::HashSet; +use std::convert::Infallible; -use crate::dbt_graph::_helpers::closure::{path_nodes, reachable}; use crate::dbt_graph::_helpers::endpoints::{endpoint_key, endpoint_names}; use crate::dbt_graph::constants::PATH_SELECTOR_SEPARATOR; use crate::dbt_graph::errors::DbtSelectionError; @@ -11,6 +11,8 @@ use crate::dbt_graph::models::{ DbtSelectionState, DbtSelectorExpansion, }; use crate::dbt_graph::types::DbtSelectorResolver; +use crate::graph_walk::main::breadth_first_reach::breadth_first_reach; +use crate::graph_walk::main::directed_path_nodes::directed_path_nodes; fn added( mut state: DbtSelectionState, @@ -30,18 +32,20 @@ fn expanded( parsed: &DbtSelectorExpansion, graph: &DbtCombinedGraph, ) -> HashSet { - let mut result: HashSet = keys.clone(); - if parsed.upstream { - for key in &keys { - result.extend(reachable(key, &graph.upstream)); + let mut reached: HashSet<&DbtGraphKey> = keys.iter().collect(); + for (requested, edges) in [ + (parsed.upstream, &graph.upstream), + (parsed.downstream, &graph.downstream), + ] { + if requested { + let mut visited: HashSet<&DbtGraphKey> = HashSet::new(); + let Ok(()) = breadth_first_reach(keys.iter(), None, &mut visited, |key| { + Ok::<_, Infallible>(edges.targets.get(*key).into_iter().flatten()) + }); + reached.extend(visited); } } - if parsed.downstream { - for key in &keys { - result.extend(reachable(key, &graph.downstream)); - } - } - result + reached.into_iter().cloned().collect() } fn path_keys( @@ -53,7 +57,20 @@ fn path_keys( let start: DbtGraphKey = endpoint_key(resolver, request.manifest_models, (start_name, raw_term))?; let end: DbtGraphKey = endpoint_key(resolver, request.manifest_models, (end_name, raw_term))?; - Ok(path_nodes((&start, &end), &graph.downstream, &graph.upstream).unwrap_or_default()) + let (mut reachable, mut path): (HashSet<&DbtGraphKey>, HashSet<&DbtGraphKey>) = + (HashSet::new(), HashSet::new()); + let Ok(found) = directed_path_nodes( + (&start, &end), + ( + |key: &&DbtGraphKey| Ok(graph.downstream.targets.get(*key).into_iter().flatten()), + |key: &&DbtGraphKey| Ok(graph.upstream.targets.get(*key).into_iter().flatten()), + ), + (&mut reachable, &mut path), + ); + if !found { + return Ok(HashSet::new()); + } + Ok(path.into_iter().cloned().collect()) } fn with_anchor(mut selection: DbtSelection, term: &str, unique_ids: &[String]) -> DbtSelection { @@ -82,10 +99,12 @@ fn anchored( .map_or(&[], Vec::as_slice); state.selection = with_anchor(state.selection, term, anchors); for unique_id in anchors { - state = added( - state, - reachable(&DbtGraphKey::dbt_model(unique_id), &graph.downstream), - ); + let start: DbtGraphKey = DbtGraphKey::dbt_model(unique_id); + let mut reached: HashSet<&DbtGraphKey> = HashSet::new(); + let Ok(()) = breadth_first_reach([&start], None, &mut reached, |key| { + Ok::<_, Infallible>(graph.downstream.targets.get(*key).into_iter().flatten()) + }); + state = added(state, reached.into_iter().cloned()); } state } diff --git a/crates/sqlbuild-core/src/dbt_graph/main/dbt_downstream_sqlbuild_models.rs b/crates/sqlbuild-core/src/dbt_graph/main/dbt_downstream_sqlbuild_models.rs index d9157a53c7..b9e41acbb5 100644 --- a/crates/sqlbuild-core/src/dbt_graph/main/dbt_downstream_sqlbuild_models.rs +++ b/crates/sqlbuild-core/src/dbt_graph/main/dbt_downstream_sqlbuild_models.rs @@ -1,8 +1,9 @@ //! SQLBuild models downstream of dbt models. -use std::collections::BTreeSet; +use crate::graph_walk::main::breadth_first_reach::breadth_first_reach; +use std::collections::{BTreeSet, HashSet}; +use std::convert::Infallible; -use crate::dbt_graph::_helpers::closure::reachable; use crate::dbt_graph::models::{ DbtCombinedGraph, DbtGraphKey, DbtGraphOwner, DbtGraphResourceType, }; @@ -15,11 +16,16 @@ pub fn dbt_downstream_sqlbuild_models( ) -> Vec { let mut names: BTreeSet = BTreeSet::new(); for unique_id in unique_ids { - for key in reachable(&DbtGraphKey::dbt_model(unique_id), &graph.downstream) { + let start: DbtGraphKey = DbtGraphKey::dbt_model(unique_id); + let mut reached: HashSet<&DbtGraphKey> = HashSet::new(); + let Ok(()) = breadth_first_reach([&start], None, &mut reached, |key| { + Ok::<_, Infallible>(graph.downstream.targets.get(*key).into_iter().flatten()) + }); + for key in reached { if key.owner == DbtGraphOwner::Sqlbuild && key.resource_type == DbtGraphResourceType::Model { - names.insert(key.name); + names.insert(key.name.clone()); } } } diff --git a/crates/sqlbuild-core/src/graph_walk/_helpers/mod.rs b/crates/sqlbuild-core/src/graph_walk/_helpers/mod.rs new file mode 100644 index 0000000000..250f973053 --- /dev/null +++ b/crates/sqlbuild-core/src/graph_walk/_helpers/mod.rs @@ -0,0 +1,2 @@ +mod node_sets; +mod ready_queues; diff --git a/crates/sqlbuild-core/src/graph_walk/_helpers/node_sets.rs b/crates/sqlbuild-core/src/graph_walk/_helpers/node_sets.rs new file mode 100644 index 0000000000..6e345276f9 --- /dev/null +++ b/crates/sqlbuild-core/src/graph_walk/_helpers/node_sets.rs @@ -0,0 +1,31 @@ +//! `NodeSet` for the standard sets. + +use std::collections::{BTreeSet, HashSet}; +use std::convert::Infallible; +use std::hash::Hash; + +use crate::graph_walk::types::NodeSet; + +impl NodeSet for HashSet { + type Error = Infallible; + + fn insert_new(&mut self, key: &K) -> Result { + Ok(!self.contains(key) && self.insert(key.clone())) + } + + fn holds(&self, key: &K) -> Result { + Ok(self.contains(key)) + } +} + +impl NodeSet for BTreeSet { + type Error = Infallible; + + fn insert_new(&mut self, key: &K) -> Result { + Ok(!self.contains(key) && self.insert(key.clone())) + } + + fn holds(&self, key: &K) -> Result { + Ok(self.contains(key)) + } +} diff --git a/crates/sqlbuild-core/src/graph_walk/_helpers/ready_queues.rs b/crates/sqlbuild-core/src/graph_walk/_helpers/ready_queues.rs new file mode 100644 index 0000000000..a8c78f5d70 --- /dev/null +++ b/crates/sqlbuild-core/src/graph_walk/_helpers/ready_queues.rs @@ -0,0 +1,26 @@ +//! `ReadyQueue` for first-in-first-out and smallest-first queues. + +use std::cmp::Reverse; +use std::collections::{BinaryHeap, VecDeque}; + +use crate::graph_walk::types::ReadyQueue; + +impl ReadyQueue for VecDeque { + fn offer(&mut self, key: K) { + self.push_back(key); + } + + fn take(&mut self) -> Option { + self.pop_front() + } +} + +impl ReadyQueue for BinaryHeap> { + fn offer(&mut self, key: K) { + self.push(Reverse(key)); + } + + fn take(&mut self) -> Option { + self.pop().map(|Reverse(key)| key) + } +} diff --git a/crates/sqlbuild-core/src/graph_walk/main/breadth_first_path.rs b/crates/sqlbuild-core/src/graph_walk/main/breadth_first_path.rs new file mode 100644 index 0000000000..1b4c0a84f8 --- /dev/null +++ b/crates/sqlbuild-core/src/graph_walk/main/breadth_first_path.rs @@ -0,0 +1,34 @@ +//! `breadth_first_path`: the shortest path from a node to the first goal it reaches. + +use std::collections::{HashSet, VecDeque}; +use std::hash::Hash; + +/// The shortest path from `origin` ending at a goal neighbour, entering only nodes `enter` allows; the origin itself is never re-entered. +pub fn breadth_first_path( + origin: K, + (mut neighbours, is_goal, enter): (N, G, E), +) -> Option> +where + K: Clone + Eq + Hash, + I: IntoIterator, + N: FnMut(&K) -> I, + G: Fn(&K) -> bool, + E: Fn(&K) -> bool, +{ + let mut visited: HashSet = HashSet::from([origin.clone()]); + let mut pending: VecDeque> = VecDeque::from([vec![origin]]); + while let Some(path) = pending.pop_front() { + let last: &K = path.last()?; + for neighbour in neighbours(last) { + let mut candidate: Vec = path.clone(); + candidate.push(neighbour.clone()); + if is_goal(&neighbour) { + return Some(candidate); + } + if enter(&neighbour) && visited.insert(neighbour) { + pending.push_back(candidate); + } + } + } + None +} diff --git a/crates/sqlbuild-core/src/graph_walk/main/breadth_first_reach.rs b/crates/sqlbuild-core/src/graph_walk/main/breadth_first_reach.rs new file mode 100644 index 0000000000..11c12df954 --- /dev/null +++ b/crates/sqlbuild-core/src/graph_walk/main/breadth_first_reach.rs @@ -0,0 +1,31 @@ +//! `breadth_first_reach`: record every node reachable from the starts, breadth first. + +use std::collections::VecDeque; + +use crate::graph_walk::types::NodeSet; + +/// Record nodes within `max_depth` edges of a start in first-visit order; a start counts only when reached, so callers including starts record them first. +pub fn breadth_first_reach( + starts: impl IntoIterator, + max_depth: Option, + visited: &mut S, + mut neighbours: N, +) -> Result<(), S::Error> +where + S: NodeSet, + I: IntoIterator, + N: FnMut(&K) -> Result, +{ + let mut frontier: VecDeque<(K, u64)> = starts.into_iter().map(|start| (start, 0)).collect(); + while let Some((current, depth)) = frontier.pop_front() { + if max_depth.is_some_and(|maximum| depth >= maximum) { + continue; + } + for neighbour in neighbours(¤t)? { + if visited.insert_new(&neighbour)? { + frontier.push_back((neighbour, depth + 1)); + } + } + } + Ok(()) +} diff --git a/crates/sqlbuild-core/src/graph_walk/main/depth_first_postorder.rs b/crates/sqlbuild-core/src/graph_walk/main/depth_first_postorder.rs new file mode 100644 index 0000000000..8b6fa900d2 --- /dev/null +++ b/crates/sqlbuild-core/src/graph_walk/main/depth_first_postorder.rs @@ -0,0 +1,33 @@ +//! `depth_first_postorder`: nodes after the nodes they lead to, depth first from each root. + +use std::collections::HashSet; +use std::hash::Hash; + +/// Every node reachable from `roots`, each after its neighbours, visiting roots and neighbours in the given order and entering each node once. +pub fn depth_first_postorder( + roots: impl IntoIterator, + mut neighbours: N, +) -> Vec +where + K: Clone + Eq + Hash, + I: IntoIterator, + N: FnMut(&K) -> I, +{ + let mut pending: Vec<(K, bool)> = roots.into_iter().map(|root| (root, false)).collect(); + pending.reverse(); + let mut entered: HashSet = HashSet::new(); + let mut ordered: Vec = Vec::new(); + while let Some((node, finished)) = pending.pop() { + if finished { + ordered.push(node); + continue; + } + if !entered.insert(node.clone()) { + continue; + } + let next: Vec = neighbours(&node).into_iter().collect(); + pending.push((node, true)); + pending.extend(next.into_iter().rev().map(|neighbour| (neighbour, false))); + } + ordered +} diff --git a/crates/sqlbuild-core/src/graph_walk/main/directed_path_nodes.rs b/crates/sqlbuild-core/src/graph_walk/main/directed_path_nodes.rs new file mode 100644 index 0000000000..26565e2dae --- /dev/null +++ b/crates/sqlbuild-core/src/graph_walk/main/directed_path_nodes.rs @@ -0,0 +1,37 @@ +//! `directed_path_nodes`: the nodes on directed paths from one node to another. + +use crate::graph_walk::main::breadth_first_reach::breadth_first_reach; +use crate::graph_walk::types::NodeSet; + +/// Record in `path` every node on a downstream path from `start` to `end`, both included; false when `end` is not downstream of `start`. +pub fn directed_path_nodes( + (start, end): (K, K), + (downstream, mut upstream): (D, U), + (reachable, path): (&mut S, &mut S), +) -> Result +where + K: Clone, + S: NodeSet, + I: IntoIterator, + J: IntoIterator, + D: FnMut(&K) -> Result, + U: FnMut(&K) -> Result, +{ + breadth_first_reach([start.clone()], None, reachable, downstream)?; + if !reachable.holds(&end)? { + return Ok(false); + } + reachable.insert_new(&start)?; + let mut pending: Vec = vec![end]; + while let Some(current) = pending.pop() { + if !path.insert_new(¤t)? { + continue; + } + for parent in upstream(¤t)? { + if reachable.holds(&parent)? { + pending.push(parent); + } + } + } + Ok(true) +} diff --git a/crates/sqlbuild-core/src/graph_walk/main/find_cycle.rs b/crates/sqlbuild-core/src/graph_walk/main/find_cycle.rs new file mode 100644 index 0000000000..9ab19255d2 --- /dev/null +++ b/crates/sqlbuild-core/src/graph_walk/main/find_cycle.rs @@ -0,0 +1,49 @@ +//! `find_cycle`: the first cycle a depth-first walk from each root meets. + +use std::collections::HashSet; +use std::hash::Hash; + +/// The path from a root to the first node met twice on it, ending with that node; finished nodes are shared across roots. +pub fn find_cycle(roots: impl IntoIterator, mut neighbours: N) -> Option> +where + K: Clone + Eq + Hash, + I: IntoIterator, + N: FnMut(&K) -> I, +{ + let mut finished: HashSet = HashSet::new(); + for root in roots { + let mut path: Vec = Vec::new(); + let mut frames: Vec> = Vec::new(); + let mut next: Option = Some(root); + loop { + if let Some(node) = next.take() + && !finished.contains(&node) + { + if path.contains(&node) { + path.push(node); + return Some(path); + } + frames.push( + neighbours(&node) + .into_iter() + .collect::>() + .into_iter(), + ); + path.push(node); + } + let Some(frame) = frames.last_mut() else { + break; + }; + match frame.next() { + Some(neighbour) => next = Some(neighbour), + None => { + frames.pop(); + if let Some(done) = path.pop() { + finished.insert(done); + } + } + } + } + } + None +} diff --git a/crates/sqlbuild-core/src/graph_walk/main/kahn_order.rs b/crates/sqlbuild-core/src/graph_walk/main/kahn_order.rs new file mode 100644 index 0000000000..d84a5ce465 --- /dev/null +++ b/crates/sqlbuild-core/src/graph_walk/main/kahn_order.rs @@ -0,0 +1,40 @@ +//! `kahn_order`: nodes in dependency order with Kahn's algorithm and a caller tie-break. + +use std::collections::HashMap; +use std::hash::Hash; + +use crate::graph_walk::types::ReadyQueue; + +/// Take ready nodes from `ready`, seeded with the zero-count `nodes` in order; each successor edge lowers a count once, and nodes left uncounted are never taken. +pub fn kahn_order( + (nodes, mut remaining): (impl IntoIterator, HashMap), + mut ready: Q, + mut successors: N, +) -> Vec +where + K: Clone + Eq + Hash, + Q: ReadyQueue, + I: IntoIterator, + N: FnMut(&K) -> I, +{ + for node in nodes { + if remaining.get(&node) == Some(&0) { + ready.offer(node); + } + } + let mut ordered: Vec = Vec::with_capacity(remaining.len()); + while let Some(current) = ready.take() { + for successor in successors(¤t) { + if let Some(count) = remaining.get_mut(&successor) + && *count > 0 + { + *count -= 1; + if *count == 0 { + ready.offer(successor); + } + } + } + ordered.push(current); + } + ordered +} diff --git a/crates/sqlbuild-core/src/graph_walk/main/mod.rs b/crates/sqlbuild-core/src/graph_walk/main/mod.rs new file mode 100644 index 0000000000..3787cafe64 --- /dev/null +++ b/crates/sqlbuild-core/src/graph_walk/main/mod.rs @@ -0,0 +1,6 @@ +pub mod breadth_first_path; +pub mod breadth_first_reach; +pub mod depth_first_postorder; +pub mod directed_path_nodes; +pub mod find_cycle; +pub mod kahn_order; diff --git a/crates/sqlbuild-core/src/graph_walk/mod.rs b/crates/sqlbuild-core/src/graph_walk/mod.rs new file mode 100644 index 0000000000..3459b08aa1 --- /dev/null +++ b/crates/sqlbuild-core/src/graph_walk/mod.rs @@ -0,0 +1,9 @@ +//! Graph walks shared by every graph shape: reachability, directed paths, orders and cycles. + +mod _helpers; +pub mod main; +pub mod models; +pub mod types; + +#[cfg(test)] +mod tests; diff --git a/crates/sqlbuild-core/src/graph_walk/models.rs b/crates/sqlbuild-core/src/graph_walk/models.rs new file mode 100644 index 0000000000..f98c6e8cd6 --- /dev/null +++ b/crates/sqlbuild-core/src/graph_walk/models.rs @@ -0,0 +1,40 @@ +//! Visited nodes kept in first-visit order. + +use std::collections::HashSet; +use std::convert::Infallible; +use std::hash::Hash; + +use crate::graph_walk::types::NodeSet; + +/// Distinct nodes in the order a walk first recorded them. +#[derive(Debug, Clone)] +pub struct VisitOrder { + /// The recorded nodes, first visit first. + pub order: Vec, + seen: HashSet, +} + +impl Default for VisitOrder { + fn default() -> Self { + Self { + order: Vec::new(), + seen: HashSet::new(), + } + } +} + +impl NodeSet for VisitOrder { + type Error = Infallible; + + fn insert_new(&mut self, key: &K) -> Result { + if !self.seen.insert(key.clone()) { + return Ok(false); + } + self.order.push(key.clone()); + Ok(true) + } + + fn holds(&self, key: &K) -> Result { + Ok(self.seen.contains(key)) + } +} diff --git a/crates/sqlbuild-core/src/graph_walk/tests.rs b/crates/sqlbuild-core/src/graph_walk/tests.rs new file mode 100644 index 0000000000..cabcb71428 --- /dev/null +++ b/crates/sqlbuild-core/src/graph_walk/tests.rs @@ -0,0 +1,6 @@ +#[path = "tests/test_graph_walk.rs"] +mod graph_walk; +#[path = "tests/helpers.rs"] +mod helpers; +#[path = "tests/test_types.rs"] +mod test_types; diff --git a/crates/sqlbuild-core/src/graph_walk/tests/helpers.rs b/crates/sqlbuild-core/src/graph_walk/tests/helpers.rs new file mode 100644 index 0000000000..0c025983f9 --- /dev/null +++ b/crates/sqlbuild-core/src/graph_walk/tests/helpers.rs @@ -0,0 +1,65 @@ +use std::collections::HashMap; + +use crate::graph_walk::main::kahn_order::kahn_order; +use crate::graph_walk::models::VisitOrder; +use crate::graph_walk::tests::test_types::KahnTestCase; +use crate::graph_walk::types::{NodeSet, ReadyQueue}; + +/// Adjacency from `(node, neighbours)` rows. +pub(super) fn adjacency( + rows: &[(&'static str, &[&'static str])], +) -> HashMap<&'static str, Vec<&'static str>> { + rows.iter() + .map(|(node, neighbours)| (*node, neighbours.to_vec())) + .collect() +} + +/// The neighbours of `node`, none when it has no row. +pub(super) fn neighbours( + edges: &HashMap<&'static str, Vec<&'static str>>, + node: &'static str, +) -> Vec<&'static str> { + edges.get(node).cloned().unwrap_or_default() +} + +/// `edges` reversed, each parent list in row order. +pub(super) fn inverted( + rows: &[(&'static str, &[&'static str])], +) -> HashMap<&'static str, Vec<&'static str>> { + let mut parents: HashMap<&'static str, Vec<&'static str>> = HashMap::new(); + for (node, children) in rows { + for child in *children { + parents.entry(*child).or_default().push(*node); + } + } + parents +} + +/// A visit order holding `starts` when they are included. +pub(super) fn recorded_starts(starts: &[&'static str], include: bool) -> VisitOrder<&'static str> { + let mut visited: VisitOrder<&'static str> = VisitOrder::default(); + for start in starts.iter().filter(|_| include) { + let Ok(_) = visited.insert_new(start); + } + visited +} + +/// The Kahn order of one test case under `ready`, counting every edge. +pub(super) fn kahn_case_order>( + test_case: &KahnTestCase, + ready: Q, +) -> Vec<&'static str> { + let edges = adjacency(test_case.edges); + let mut remaining: HashMap<&'static str, usize> = + test_case.nodes.iter().map(|node| (*node, 0)).collect(); + for (_, successors) in test_case.edges { + for successor in *successors { + *remaining.entry(successor).or_default() += 1; + } + } + kahn_order( + (test_case.nodes.iter().copied(), remaining), + ready, + |node: &&'static str| neighbours(&edges, node), + ) +} diff --git a/crates/sqlbuild-core/src/graph_walk/tests/test_graph_walk.rs b/crates/sqlbuild-core/src/graph_walk/tests/test_graph_walk.rs new file mode 100644 index 0000000000..d1a311f138 --- /dev/null +++ b/crates/sqlbuild-core/src/graph_walk/tests/test_graph_walk.rs @@ -0,0 +1,267 @@ +use std::cmp::Reverse; +use std::collections::{BTreeSet, BinaryHeap, VecDeque}; + +use crate::graph_walk::main::breadth_first_path::breadth_first_path; +use crate::graph_walk::main::breadth_first_reach::breadth_first_reach; +use crate::graph_walk::main::depth_first_postorder::depth_first_postorder; +use crate::graph_walk::main::directed_path_nodes::directed_path_nodes; +use crate::graph_walk::main::find_cycle::find_cycle; +use crate::graph_walk::models::VisitOrder; +use crate::graph_walk::tests::helpers::{ + adjacency, inverted, kahn_case_order, neighbours, recorded_starts, +}; +use crate::graph_walk::tests::test_types::{ + CycleTestCase, GoalPathTestCase, KahnTestCase, OrderTestCase, PathTestCase, ReachTestCase, +}; + +#[test] +fn given_starts_when_reaching_breadth_first_then_records_nodes_in_first_visit_order() { + let test_cases = [ + ReachTestCase { + description: "starts are left out unless reached again", + edges: &[("a", &["b", "c"]), ("b", &["d", "a"]), ("c", &["d"])], + starts: &["a"], + include_starts: false, + max_depth: None, + expected_order: vec!["b", "c", "d", "a"], + }, + ReachTestCase { + description: "recorded starts lead and are not expanded twice", + edges: &[("a", &["c"]), ("b", &["a", "d"]), ("c", &["b"])], + starts: &["b", "a", "b"], + include_starts: true, + max_depth: None, + expected_order: vec!["b", "a", "d", "c"], + }, + ReachTestCase { + description: "a depth limit stops expansion at that distance", + edges: &[("a", &["b"]), ("b", &["c"]), ("c", &["d"])], + starts: &["a"], + include_starts: false, + max_depth: Some(2), + expected_order: vec!["b", "c"], + }, + ReachTestCase { + description: "a zero depth limit reaches nothing", + edges: &[("a", &["b"])], + starts: &["a"], + include_starts: false, + max_depth: Some(0), + expected_order: vec![], + }, + ]; + for test_case in test_cases { + let edges = adjacency(test_case.edges); + let mut visited: VisitOrder<&str> = + recorded_starts(test_case.starts, test_case.include_starts); + let Ok(()) = breadth_first_reach( + test_case.starts.iter().copied(), + test_case.max_depth, + &mut visited, + |node| Ok(neighbours(&edges, node)), + ); + assert_eq!( + visited.order, test_case.expected_order, + "{}", + test_case.description + ); + } +} + +#[test] +fn given_endpoints_when_collecting_path_nodes_then_keeps_nodes_between_them() { + let test_cases = [ + PathTestCase { + description: "branches off the paths are left out", + edges: &[("a", &["b", "x"]), ("b", &["c"]), ("x", &["y"]), ("c", &[])], + endpoints: ("a", "c"), + expected_nodes: Some(vec!["a", "b", "c"]), + }, + PathTestCase { + description: "parallel paths are all kept", + edges: &[ + ("a", &["b", "c"]), + ("b", &["d"]), + ("c", &["d"]), + ("z", &["d"]), + ], + endpoints: ("a", "d"), + expected_nodes: Some(vec!["a", "b", "c", "d"]), + }, + PathTestCase { + description: "an end not downstream of the start has no path", + edges: &[("a", &["b"]), ("c", &["a"])], + endpoints: ("a", "c"), + expected_nodes: None, + }, + ]; + for test_case in test_cases { + let downstream = adjacency(test_case.edges); + let upstream = inverted(test_case.edges); + let mut reachable: BTreeSet<&str> = BTreeSet::new(); + let mut path: BTreeSet<&str> = BTreeSet::new(); + let Ok(found) = directed_path_nodes( + test_case.endpoints, + ( + |node: &&'static str| Ok(neighbours(&downstream, node)), + |node: &&'static str| Ok(neighbours(&upstream, node)), + ), + (&mut reachable, &mut path), + ); + let nodes: Option> = found.then(|| path.into_iter().collect()); + assert_eq!(nodes, test_case.expected_nodes, "{}", test_case.description); + } +} + +#[test] +fn given_goals_when_searching_breadth_first_then_returns_the_shortest_path() { + let test_cases = [ + GoalPathTestCase { + description: "the first goal met on the shortest path ends the search", + edges: &[("a", &["b", "c"]), ("b", &["g"]), ("c", &["h"])], + origin: "a", + goals: &["g", "h"], + blocked: &[], + expected_path: Some(vec!["a", "b", "g"]), + }, + GoalPathTestCase { + description: "the origin can be the goal that closes a cycle", + edges: &[("a", &["b"]), ("b", &["a"])], + origin: "a", + goals: &["a"], + blocked: &[], + expected_path: Some(vec!["a", "b", "a"]), + }, + GoalPathTestCase { + description: "blocked nodes are not entered", + edges: &[("a", &["b"]), ("b", &["g"])], + origin: "a", + goals: &["g"], + blocked: &["b"], + expected_path: None, + }, + ]; + for test_case in test_cases { + let edges = adjacency(test_case.edges); + let path: Option> = breadth_first_path( + test_case.origin, + ( + |node: &&'static str| neighbours(&edges, node), + |node: &&'static str| test_case.goals.contains(node), + |node: &&'static str| !test_case.blocked.contains(node), + ), + ); + assert_eq!(path, test_case.expected_path, "{}", test_case.description); + } +} + +#[test] +fn given_counts_when_ordering_first_in_first_out_then_keeps_node_order_among_ready_nodes() { + let test_cases = [ + KahnTestCase { + description: "ready nodes leave in node order", + edges: &[("c", &["a"]), ("b", &["a"]), ("a", &[])], + nodes: &["c", "b", "a"], + expected_order: vec!["c", "b", "a"], + }, + KahnTestCase { + description: "repeated edges lower the count once each", + edges: &[("a", &["b", "b"]), ("b", &[])], + nodes: &["a", "b"], + expected_order: vec!["a", "b"], + }, + KahnTestCase { + description: "nodes on a cycle are left out", + edges: &[("a", &["b"]), ("b", &["c"]), ("c", &["b"])], + nodes: &["a", "b", "c"], + expected_order: vec!["a"], + }, + ]; + for test_case in test_cases { + let order: Vec<&str> = kahn_case_order(&test_case, VecDeque::new()); + assert_eq!(order, test_case.expected_order, "{}", test_case.description); + } +} + +#[test] +fn given_counts_when_ordering_smallest_first_then_takes_the_smallest_ready_node() { + let test_cases = [KahnTestCase { + description: "the smallest ready node leaves first", + edges: &[("c", &["a"]), ("b", &["a"]), ("a", &[])], + nodes: &["c", "b", "a"], + expected_order: vec!["b", "c", "a"], + }]; + for test_case in test_cases { + let order: Vec<&str> = + kahn_case_order(&test_case, BinaryHeap::>::new()); + assert_eq!(order, test_case.expected_order, "{}", test_case.description); + } +} + +#[test] +fn given_roots_when_ordering_depth_first_then_places_nodes_after_their_neighbours() { + let test_cases = [ + OrderTestCase { + description: "neighbours come first in their given order", + edges: &[("a", &["c", "b"]), ("b", &["d"]), ("c", &["d"])], + roots: &["a"], + expected_order: vec!["d", "c", "b", "a"], + }, + OrderTestCase { + description: "later roots skip nodes already placed", + edges: &[("b", &["c"]), ("a", &["c"])], + roots: &["b", "a"], + expected_order: vec!["c", "b", "a"], + }, + OrderTestCase { + description: "a cycle back to an entered node is not followed", + edges: &[("a", &["b"]), ("b", &["a"])], + roots: &["a"], + expected_order: vec!["b", "a"], + }, + ]; + for test_case in test_cases { + let edges = adjacency(test_case.edges); + let order: Vec<&str> = depth_first_postorder(test_case.roots.iter().copied(), |node| { + neighbours(&edges, node) + }); + assert_eq!(order, test_case.expected_order, "{}", test_case.description); + } +} + +#[test] +fn given_roots_when_searching_for_cycles_then_returns_the_path_to_the_repeat() { + let test_cases = [ + CycleTestCase { + description: "the path runs from the root to the repeated node", + edges: &[("a", &["b"]), ("b", &["c"]), ("c", &["b"])], + roots: &["a"], + expected_cycle: Some(vec!["a", "b", "c", "b"]), + }, + CycleTestCase { + description: "shared finished nodes are not a cycle", + edges: &[("a", &["c"]), ("b", &["c"]), ("c", &[])], + roots: &["a", "b"], + expected_cycle: None, + }, + CycleTestCase { + description: "a self edge is a cycle", + edges: &[("a", &["a"])], + roots: &["a"], + expected_cycle: Some(vec!["a", "a"]), + }, + CycleTestCase { + description: "a later root finds a cycle the earlier roots missed", + edges: &[("a", &[]), ("b", &["c"]), ("c", &["b"])], + roots: &["a", "b"], + expected_cycle: Some(vec!["b", "c", "b"]), + }, + ]; + for test_case in test_cases { + let edges = adjacency(test_case.edges); + let cycle: Option> = find_cycle(test_case.roots.iter().copied(), |node| { + neighbours(&edges, node) + }); + assert_eq!(cycle, test_case.expected_cycle, "{}", test_case.description); + } +} diff --git a/crates/sqlbuild-core/src/graph_walk/tests/test_types.rs b/crates/sqlbuild-core/src/graph_walk/tests/test_types.rs new file mode 100644 index 0000000000..16a8025d68 --- /dev/null +++ b/crates/sqlbuild-core/src/graph_walk/tests/test_types.rs @@ -0,0 +1,47 @@ +pub(super) type Rows = &'static [(&'static str, &'static [&'static str])]; + +pub(super) struct ReachTestCase { + pub(super) description: &'static str, + pub(super) edges: Rows, + pub(super) starts: &'static [&'static str], + pub(super) include_starts: bool, + pub(super) max_depth: Option, + pub(super) expected_order: Vec<&'static str>, +} + +pub(super) struct PathTestCase { + pub(super) description: &'static str, + pub(super) edges: Rows, + pub(super) endpoints: (&'static str, &'static str), + pub(super) expected_nodes: Option>, +} + +pub(super) struct GoalPathTestCase { + pub(super) description: &'static str, + pub(super) edges: Rows, + pub(super) origin: &'static str, + pub(super) goals: &'static [&'static str], + pub(super) blocked: &'static [&'static str], + pub(super) expected_path: Option>, +} + +pub(super) struct KahnTestCase { + pub(super) description: &'static str, + pub(super) edges: Rows, + pub(super) nodes: &'static [&'static str], + pub(super) expected_order: Vec<&'static str>, +} + +pub(super) struct OrderTestCase { + pub(super) description: &'static str, + pub(super) edges: Rows, + pub(super) roots: &'static [&'static str], + pub(super) expected_order: Vec<&'static str>, +} + +pub(super) struct CycleTestCase { + pub(super) description: &'static str, + pub(super) edges: Rows, + pub(super) roots: &'static [&'static str], + pub(super) expected_cycle: Option>, +} diff --git a/crates/sqlbuild-core/src/graph_walk/types.rs b/crates/sqlbuild-core/src/graph_walk/types.rs new file mode 100644 index 0000000000..684d214b36 --- /dev/null +++ b/crates/sqlbuild-core/src/graph_walk/types.rs @@ -0,0 +1,19 @@ +//! Visited-node sets and ready queues the graph walks are generic over. + +/// A set of graph nodes a walk records and queries, possibly through a fallible host. +pub trait NodeSet { + /// The failure a fallible set or its walk's neighbour lookup reports. + type Error; + /// Record `key`, returning whether it was absent. + fn insert_new(&mut self, key: &K) -> Result; + /// Whether `key` is recorded. + fn holds(&self, key: &K) -> Result; +} + +/// Nodes a topological walk may take next, in the caller's tie-break order. +pub trait ReadyQueue { + /// Offer a node whose dependencies are all taken. + fn offer(&mut self, key: K); + /// The next node to take. + fn take(&mut self) -> Option; +} diff --git a/crates/sqlbuild-core/src/lib.rs b/crates/sqlbuild-core/src/lib.rs index 99383f712f..2569bf6dc1 100644 --- a/crates/sqlbuild-core/src/lib.rs +++ b/crates/sqlbuild-core/src/lib.rs @@ -30,6 +30,7 @@ pub mod dlt_destination; pub mod event_export; pub mod format_command; pub mod freshness_text; +pub mod graph_walk; pub mod identifier_text; pub mod ingestr_command; pub mod init_command; diff --git a/crates/sqlbuild-core/src/load_command/_helpers/graph.rs b/crates/sqlbuild-core/src/load_command/_helpers/graph.rs index 2d337e6e96..be1c836485 100644 --- a/crates/sqlbuild-core/src/load_command/_helpers/graph.rs +++ b/crates/sqlbuild-core/src/load_command/_helpers/graph.rs @@ -1,7 +1,10 @@ //! Loader graph lookups: dependencies, closures and execution order. use std::collections::{BTreeSet, HashMap}; +use std::convert::Infallible; +use crate::graph_walk::main::breadth_first_reach::breadth_first_reach; +use crate::graph_walk::main::depth_first_postorder::depth_first_postorder; use crate::load_command::models::LoadGraph; /// Indexed views of a load graph, as the Python dictionaries keyed them. @@ -50,30 +53,25 @@ impl GraphIndex { /// The loader and every loader it transitively depends on. pub(crate) fn upstream_closure(&self, loader: &str) -> BTreeSet { - let mut closure: BTreeSet = BTreeSet::new(); - let mut pending: Vec = vec![loader.to_owned()]; - while let Some(next) = pending.pop() { - if closure.insert(next.clone()) { - pending.extend(self.dependencies(&next).iter().cloned()); - } - } + let mut closure: BTreeSet = BTreeSet::from([loader.to_owned()]); + let Ok(()) = breadth_first_reach([loader.to_owned()], None, &mut closure, |next| { + Ok::<_, Infallible>(self.dependencies(next).iter().cloned()) + }); closure } /// The loader, every loader depending on it transitively, and all their sources. pub(crate) fn downstream_closure(&self, loader: &str) -> (BTreeSet, BTreeSet) { - let mut loaders: BTreeSet = BTreeSet::new(); - let mut pending: Vec = vec![loader.to_owned()]; - while let Some(next) = pending.pop() { - if !loaders.insert(next.clone()) { - continue; - } - for (candidate, dependencies) in &self.upstream { - if dependencies.contains(&next) { - pending.push(candidate.clone()); - } - } - } + let mut loaders: BTreeSet = BTreeSet::from([loader.to_owned()]); + let Ok(()) = breadth_first_reach([loader.to_owned()], None, &mut loaders, |next| { + Ok::<_, Infallible>( + self.upstream + .iter() + .filter(|(_, dependencies)| dependencies.contains(next)) + .map(|(candidate, _)| candidate.clone()) + .collect::>(), + ) + }); let sources: BTreeSet = loaders .iter() .flat_map(|name| { @@ -89,28 +87,12 @@ impl GraphIndex { /// `names` ordered dependencies first, visiting in sorted order. pub(crate) fn topological_order(&self, names: &BTreeSet) -> Vec { - let mut ordered: Vec = Vec::new(); - let mut visited: BTreeSet = BTreeSet::new(); - for name in names { - self.visit(name, names, (&mut ordered, &mut visited)); - } - ordered - } - - fn visit( - &self, - name: &str, - names: &BTreeSet, - (ordered, visited): (&mut Vec, &mut BTreeSet), - ) { - if !visited.insert(name.to_owned()) { - return; - } - for dependency in self.dependencies(name) { - if names.contains(dependency) { - self.visit(dependency, names, (ordered, visited)); - } - } - ordered.push(name.to_owned()); + depth_first_postorder(names.iter().cloned(), |name| { + self.dependencies(name) + .iter() + .filter(|dependency| names.contains(*dependency)) + .cloned() + .collect::>() + }) } } diff --git a/crates/sqlbuild-core/src/load_command/_helpers/selection_sets.rs b/crates/sqlbuild-core/src/load_command/_helpers/selection_sets.rs index 17635232b1..0499eecbdf 100644 --- a/crates/sqlbuild-core/src/load_command/_helpers/selection_sets.rs +++ b/crates/sqlbuild-core/src/load_command/_helpers/selection_sets.rs @@ -1,7 +1,9 @@ //! Applying load selectors and excludes to the selected source and loader sets. use std::collections::BTreeSet; +use std::convert::Infallible; +use crate::graph_walk::main::breadth_first_reach::breadth_first_reach; use crate::load_command::_helpers::graph::GraphIndex; use crate::load_command::constants::UPSTREAM_MARK; use crate::load_command::errors::UnknownLoadSelector; @@ -177,14 +179,16 @@ impl Selection { needed.insert(loader.clone()); } } - let mut pending: Vec = needed.iter().cloned().collect(); - while let Some(loader) = pending.pop() { - for dependency in index.dependencies(&loader) { - if self.loaders.contains(dependency) && needed.insert(dependency.clone()) { - pending.push(dependency.clone()); - } - } - } + let starts: Vec = needed.iter().cloned().collect(); + let Ok(()) = breadth_first_reach(starts, None, &mut needed, |loader| { + Ok::<_, Infallible>( + index + .dependencies(loader) + .iter() + .filter(|dependency| self.loaders.contains(*dependency)) + .cloned(), + ) + }); self.loaders.retain(|loader| needed.contains(loader)); } } diff --git a/crates/sqlbuild-discovery/src/project_validation/_helpers/python_nodes.rs b/crates/sqlbuild-discovery/src/project_validation/_helpers/python_nodes.rs index 5547a19d0f..c8c784d9aa 100644 --- a/crates/sqlbuild-discovery/src/project_validation/_helpers/python_nodes.rs +++ b/crates/sqlbuild-discovery/src/project_validation/_helpers/python_nodes.rs @@ -1,6 +1,9 @@ //! Python node dependency checks: known targets, no SQL inputs on loaders, no cycles. use std::collections::{HashMap, HashSet}; +use std::hash::{Hash, Hasher}; + +use sqlbuild_core::graph_walk::main::find_cycle::find_cycle as find_dependency_cycle; use crate::project_validation::_helpers::conflicts::conflict; use crate::project_validation::models::{DependencyFact, NodeFact, NodeKind, ValidationFailure}; @@ -172,46 +175,39 @@ fn find_cycle( next: impl Fn(usize) -> Vec, label: &str, ) -> Result<(), ValidationFailure> { - let mut search: CycleSearch<'_, _> = CycleSearch { - nodes, - next, - label, - path: Vec::new(), - visited: HashSet::new(), + let named = |index: usize| NamedNode { + name: &nodes[index].name, + index, }; - for root in roots { - search.path.clear(); - search.visit(*root)?; - } - Ok(()) + let Some(cycle) = find_dependency_cycle(roots.iter().map(|root| named(*root)), |node| { + next(node.index) + .into_iter() + .map(named) + .collect::>>() + }) else { + return Ok(()); + }; + let names: Vec<&str> = cycle.iter().map(|node| node.name).collect(); + Err(conflict(format!("{label}: {}", names.join(" -> ")))) } -/// One depth-first cycle search: the nodes on the current path and those already cleared. -struct CycleSearch<'a, F> { - nodes: &'a [NodeFact], - next: F, - label: &'a str, - path: Vec<&'a str>, - visited: HashSet<&'a str>, +/// A node position whose identity in a cycle search is its name. +#[derive(Clone, Copy)] +struct NamedNode<'a> { + name: &'a str, + index: usize, } -impl<'a, F: Fn(usize) -> Vec> CycleSearch<'a, F> { - fn visit(&mut self, index: usize) -> Result<(), ValidationFailure> { - let name: &'a str = &self.nodes[index].name; - if self.visited.contains(name) { - return Ok(()); - } - if self.path.contains(&name) { - let mut cycle: Vec<&str> = self.path.clone(); - cycle.push(name); - return Err(conflict(format!("{}: {}", self.label, cycle.join(" -> ")))); - } - self.path.push(name); - for dependency in (self.next)(index) { - self.visit(dependency)?; - } - self.path.pop(); - self.visited.insert(name); - Ok(()) +impl PartialEq for NamedNode<'_> { + fn eq(&self, other: &Self) -> bool { + self.name == other.name + } +} + +impl Eq for NamedNode<'_> {} + +impl Hash for NamedNode<'_> { + fn hash(&self, state: &mut H) { + self.name.hash(state); } } diff --git a/crates/sqlbuild-pysource/src/macro_modules/_helpers/load_plan.rs b/crates/sqlbuild-pysource/src/macro_modules/_helpers/load_plan.rs index b9f3be44fb..1816f93c95 100644 --- a/crates/sqlbuild-pysource/src/macro_modules/_helpers/load_plan.rs +++ b/crates/sqlbuild-pysource/src/macro_modules/_helpers/load_plan.rs @@ -4,6 +4,7 @@ use std::cmp::Reverse; use std::collections::{BTreeSet, BinaryHeap, HashMap}; use sha2::{Digest, Sha256}; +use sqlbuild_core::graph_walk::main::kahn_order::kahn_order; use crate::macro_modules::constants::{SYNTHETIC_DIGEST_LENGTH, SYNTHETIC_PACKAGE_PREFIX}; use crate::macro_modules::models::MacroModuleSpec; @@ -50,22 +51,13 @@ pub(crate) fn load_order(modules: &[MacroModuleSpec]) -> Vec { .push(&module.name); } } - let mut ready: BinaryHeap> = remaining - .iter() - .filter(|(_, count)| **count == 0) - .map(|(name, _)| Reverse(*name)) - .collect(); - let mut order: Vec = Vec::new(); - while let Some(Reverse(name)) = ready.pop() { - order.push(name.to_owned()); - for dependent in dependents.get(name).into_iter().flatten() { - if let Some(count) = remaining.get_mut(dependent) { - *count -= 1; - if *count == 0 { - ready.push(Reverse(dependent)); - } - } - } - } - order + let names: Vec<&str> = remaining.keys().copied().collect(); + kahn_order( + (names, remaining), + BinaryHeap::>::new(), + |name| dependents.get(name).cloned().unwrap_or_default(), + ) + .into_iter() + .map(str::to_owned) + .collect() } diff --git a/crates/sqlbuild-pysource/src/macro_modules/_helpers/module_analysis.rs b/crates/sqlbuild-pysource/src/macro_modules/_helpers/module_analysis.rs index a627a23261..6d792fd38f 100644 --- a/crates/sqlbuild-pysource/src/macro_modules/_helpers/module_analysis.rs +++ b/crates/sqlbuild-pysource/src/macro_modules/_helpers/module_analysis.rs @@ -3,8 +3,14 @@ use ruff_python_ast::ExceptHandler; use ruff_python_ast::{ModModule, Stmt, StmtFunctionDef, StmtImportFrom}; use std::collections::{HashMap, HashSet}; +use std::convert::Infallible; use std::iter::once; +use sqlbuild_core::graph_walk::main::breadth_first_reach::breadth_first_reach; +use sqlbuild_core::graph_walk::main::find_cycle::find_cycle; +use sqlbuild_core::graph_walk::models::VisitOrder; +use sqlbuild_core::graph_walk::types::NodeSet; + use crate::cpython_tree::models::CpythonNode; use crate::macro_modules::_helpers::declarations::module_exports; use crate::macro_modules::_helpers::function_calls::{ @@ -292,21 +298,20 @@ fn export_dependencies( ) -> HashMap> { let mut dependencies: HashMap> = HashMap::new(); for export in exports { + let mut visited: VisitOrder<&str> = VisitOrder::default(); + let Ok(_) = visited.insert_new(&export.as_str()); + let Ok(()) = breadth_first_reach([export.as_str()], None, &mut visited, |function| { + Ok::<_, Infallible>(helper_calls[*function].iter().map(String::as_str)) + }); let mut found: Vec = Vec::new(); - let mut pending: std::collections::VecDeque<&str> = - std::collections::VecDeque::from([export.as_str()]); - let mut visited: HashSet<&str> = HashSet::new(); - while let Some(function) = pending.pop_front() { - if !visited.insert(function) { - continue; - } + for function in visited.order { found.extend(direct_dependencies[function].iter().cloned()); - for called in &helper_calls[function] { - if exports.contains(called) { - found.push(called.clone()); - } - pending.push_back(called); - } + found.extend( + helper_calls[function] + .iter() + .filter(|called| exports.contains(*called)) + .cloned(), + ); } dependencies.insert(export.clone(), unique(found)); } @@ -320,38 +325,23 @@ fn validate_local_call_cycles( ) -> Result<(), String> { let mut sorted: Vec<&String> = exports.iter().collect(); sorted.sort(); - let mut completed: HashSet = HashSet::new(); - for export in sorted { - completed = visit_cycle(export, (helper_calls, &[]), completed).map_err(|cycle| { - format!( - "Macro call cycle in '{display}': {}", - cycle.join(CYCLE_SEPARATOR) - ) - })?; - } - Ok(()) + let Some(cycle) = repeated_path(sorted, helper_calls) else { + return Ok(()); + }; + let names: Vec<&str> = cycle.iter().map(|name| name.as_str()).collect(); + Err(format!( + "Macro call cycle in '{display}': {}", + names.join(CYCLE_SEPARATOR) + )) } -/// Depth-first cycle search returning the completed names; the error is the cycle path. -pub(crate) fn visit_cycle( - name: &str, - (edges, active): (&HashMap>, &[String]), - completed: HashSet, -) -> Result, Vec> { - if let Some(start) = active.iter().position(|item| item == name) { - let mut cycle: Vec = active[start..].to_vec(); - cycle.push(name.to_owned()); - return Err(cycle); - } - if completed.contains(name) { - return Ok(completed); - } - let mut path: Vec = active.to_vec(); - path.push(name.to_owned()); - let mut updated: HashSet = completed; - for dependency in &edges[name] { - updated = visit_cycle(dependency, (edges, &path), updated)?; - } - updated.insert(name.to_owned()); - Ok(updated) +/// The first dependency cycle depth first from `roots` in order, from its repeated name back to itself. +pub(crate) fn repeated_path<'a>( + roots: Vec<&'a String>, + edges: &'a HashMap>, +) -> Option> { + let path: Vec<&String> = find_cycle(roots, |name| edges.get(*name).into_iter().flatten())?; + let repeated: &String = path.last()?; + let start: usize = path.iter().position(|name| name == &repeated)?; + Some(path[start..].to_vec()) } diff --git a/crates/sqlbuild-pysource/src/macro_modules/_helpers/project_analysis.rs b/crates/sqlbuild-pysource/src/macro_modules/_helpers/project_analysis.rs index 5d60896932..5eccae6498 100644 --- a/crates/sqlbuild-pysource/src/macro_modules/_helpers/project_analysis.rs +++ b/crates/sqlbuild-pysource/src/macro_modules/_helpers/project_analysis.rs @@ -11,7 +11,7 @@ use crate::macro_modules::_helpers::declarations::{ top_level_functions, }; use crate::macro_modules::_helpers::module_analysis::{ - ModuleAnalysis, ModuleSet, ParsedModule, analyze_module, parsed_module, visit_cycle, + ModuleAnalysis, ModuleSet, ParsedModule, analyze_module, parsed_module, repeated_path, }; use crate::macro_modules::_helpers::module_names::{module_name, package_name}; use crate::macro_modules::constants::CYCLE_SEPARATOR; @@ -123,18 +123,16 @@ fn validate_import_cycles( .collect(); let mut sorted: Vec<&String> = edges.keys().collect(); sorted.sort(); - let mut completed: HashSet = HashSet::new(); - for name in sorted { - completed = visit_cycle(name, (&edges, &[]), completed).map_err(|cycle| { - let repeated: &str = cycle.last().map_or("", String::as_str); - format!( - "Macro import cycle in '{}': {}", - project.sources[files[repeated]].relative_path.display, - cycle.join(CYCLE_SEPARATOR) - ) - })?; - } - Ok(()) + let Some(cycle) = repeated_path(sorted, &edges) else { + return Ok(()); + }; + let repeated: &str = cycle.last().map_or("", |name| name.as_str()); + let names: Vec<&str> = cycle.iter().map(|name| name.as_str()).collect(); + Err(format!( + "Macro import cycle in '{}': {}", + project.sources[files[repeated]].relative_path.display, + names.join(CYCLE_SEPARATOR) + )) } fn source_digest(contents: &str) -> String { diff --git a/crates/sqlbuild-pysource/src/rule_evidence/main/import_closure.rs b/crates/sqlbuild-pysource/src/rule_evidence/main/import_closure.rs index f8c040ac98..05ceadd2f0 100644 --- a/crates/sqlbuild-pysource/src/rule_evidence/main/import_closure.rs +++ b/crates/sqlbuild-pysource/src/rule_evidence/main/import_closure.rs @@ -1,8 +1,11 @@ //! `import_closure`: the repository-owned helper modules one rule module imports. use std::collections::BTreeSet; +use std::convert::Infallible; use std::path::{Path, PathBuf}; +use sqlbuild_core::graph_walk::main::breadth_first_reach::breadth_first_reach; + use crate::cpython_tree::main::parse_cpython_module::parse_cpython_module; use crate::cpython_tree::main::walk_breadth_first::walk_breadth_first; use crate::cpython_tree::models::{CpythonNode, ParsedSource}; @@ -17,27 +20,21 @@ pub fn import_closure( python_version: (u8, u8), ) -> Vec { let rules_root: PathBuf = resolve(&project_dir.join(RULES_DIRECTORY_NAME)); - let mut pending: Vec = vec![source_path.to_path_buf()]; - let mut visited: BTreeSet = BTreeSet::from([source_path.to_path_buf()]); let mut helpers: BTreeSet = BTreeSet::new(); - while let Some(current) = pending.pop() { + let Ok(()) = breadth_first_reach([source_path.to_path_buf()], None, &mut helpers, |current| { let mut candidates: BTreeSet = - package_inits(¤t, &rules_root).into_iter().collect(); + package_inits(current, &rules_root).into_iter().collect(); let parsed: Option = - parse_cpython_module(&read_source(¤t), python_version); + parse_cpython_module(&read_source(current), python_version); if let Some(parsed) = &parsed { let roots: Vec> = parsed.module.body.iter().map(CpythonNode::Stmt).collect(); for node in walk_breadth_first(roots) { - candidates.extend(local_import_paths(&node, ¤t, &rules_root)); - } - } - for candidate in candidates { - if visited.insert(candidate.clone()) { - helpers.insert(candidate.clone()); - pending.push(candidate); + candidates.extend(local_import_paths(&node, current, &rules_root)); } } - } + candidates.remove(source_path); + Ok::<_, Infallible>(candidates) + }); helpers.into_iter().collect() } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/analysis/binding_catalog.rs b/crates/sqlbuild-python/src/bindings/_helpers/analysis/binding_catalog.rs index e9b5ecf5ec..d14c6b8b17 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/analysis/binding_catalog.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/analysis/binding_catalog.rs @@ -11,11 +11,11 @@ use sqlbuild_analysis::semantic_validation::types::{BindingRequest, DiagnosticRo use crate::bindings::_helpers::analysis::conversions; use crate::bindings::_helpers::boundary::panics::compiler_error; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::models::{CatalogInput, ProjectCatalog}; use crate::bindings::types::CompilerDetach; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - /// Dialects whose quoted identifiers bind case-sensitively unless the session says otherwise. pub(crate) const CASE_SENSITIVE_BINDING_DIALECTS: [&str; 2] = ["snowflake", "postgresql"]; /// Adapter dialect names and the native dialect each validates as. @@ -322,12 +322,8 @@ pub(crate) fn schema_validations<'py>( None => groups.push((catalog, own, vec![index])), } } - let result_type: Bound<'py, PyAny> = py - .import(NATIVE_RECORDS_MODULE)? - .getattr("SqlBindingResult")?; - let diagnostic_type: Bound<'py, PyAny> = py - .import(NATIVE_RECORDS_MODULE)? - .getattr("SqlBindingDiagnostic")?; + let result_type: Bound<'py, PyAny> = registry::class(py, RecordId::SqlBindingResult)?; + let diagnostic_type: Bound<'py, PyAny> = registry::class(py, RecordId::SqlBindingDiagnostic)?; let empty: Bound<'py, PyAny> = result_type.call0()?; let mut results: Vec> = vec![empty; requests.len()]; for (catalog, any_explicit, members) in groups { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/analysis/explicit_references.rs b/crates/sqlbuild-python/src/bindings/_helpers/analysis/explicit_references.rs index c9b585108b..47d9c379c9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/analysis/explicit_references.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/analysis/explicit_references.rs @@ -1,6 +1,5 @@ //! Compile's explicit-reference checks over plain project facts, run natively. -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class; use pyo3::prelude::{ Bound, PyAny, PyAnyMethods, PyErr, PyModule, PyModuleMethods, PyResult, Python, }; @@ -27,17 +26,14 @@ use sqlbuild_pysource::literal_sql::models::FunctionSource; use crate::bindings::_helpers::analysis::reference_facts::{ProjectFacts, project_facts}; use crate::bindings::_helpers::boundary::panics::compiler_error; use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::types::CompilerDetach; const EXPLICIT_REFERENCES_CONTEXT: &str = "native explicit-reference checks"; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; -const COMPILE_INPUT_ERROR: &str = "CompileInputError"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const REFERENCE_MODELS_MODULE: &str = "sqlbuild.compiler.references.models"; -const CONTRACT_MODELS_MODULE: &str = "sqlbuild._native"; -const REFERENCE_TYPES_MODULE: &str = "sqlbuild._native"; -const LOADER_DEFINITION_MODULE: &str = "sqlbuild.python_nodes.main.read_loader_definition"; -const NATIVE_MODULE: &str = "sqlbuild._native"; const BUILTINS_MODULE: &str = "builtins"; const VALUE_PLACEHOLDER: &str = "__sqlbuild_python_value_"; const HARD_CODED_ALLOWED: &str = "hard-coded relation names"; @@ -142,9 +138,7 @@ fn explicit_reference_report<'py>( { Ok(report) => python_report(py, &report, &owners), Err(ReferenceFailure::CompileInput(message)) => Err(PyErr::from_value( - py.import(COMPILE_ERRORS_MODULE)? - .getattr(COMPILE_INPUT_ERROR)? - .call1((message,))?, + registry::error(py, ErrorId::CompileInput)?.call1((message,))?, )), Err(ReferenceFailure::UnknownDialect(message)) => Err(compiler_error(message)), Err(ReferenceFailure::Internal(reason)) => Err(compiler_error(native_failure( @@ -173,7 +167,7 @@ fn python_sql_owners<'py>( } } owners.extend(loader_owners(project, discovered_inputs)?); - let hook_names = imported(py, NATIVE_MODULE, "model_python_hook_names")?; + let hook_names = registry::native(py, NativeAttr::ModelPythonHookNames)?; let mut attached: BTreeMap> = BTreeMap::new(); for model in project.getattr("models")?.try_iter()? { let model: Bound<'py, PyAny> = model?; @@ -222,7 +216,7 @@ fn loader_owners<'py>( .insert(source.getattr("name")?.extract()?); } } - let read_definition = imported(py, LOADER_DEFINITION_MODULE, "read_loader_definition")?; + let read_definition = host::entry(py, UserCodeHost::ReadLoaderDefinition)?; let mut owners: Vec> = Vec::new(); for loader in discovered_inputs.getattr("loader_functions")?.try_iter()? { let loader: Bound<'py, PyAny> = loader?; @@ -314,15 +308,11 @@ fn python_report<'py>( report: &ReferenceReport, owners: &[PythonSqlOwner<'py>], ) -> PyResult> { - let owner_kind: Bound<'py, PyAny> = - imported(py, REFERENCE_TYPES_MODULE, "HardCodedRelationOwnerKind")?; - let compile_models = py.import(COMPILE_MODELS_MODULE)?; + let owner_kind: Bound<'py, PyAny> = registry::class(py, RecordId::HardCodedRelationOwnerKind)?; let reference_models = py.import(REFERENCE_MODELS_MODULE)?; - let source_location: Bound<'py, PyAny> = py - .import(CONTRACT_MODELS_MODULE)? - .getattr("SourceLocation")?; + let source_location: Bound<'py, PyAny> = registry::class(py, RecordId::SourceLocation)?; let path_class: Bound<'py, PyAny> = py.import("pathlib")?.getattr("Path")?; - let compiler_diagnostic: Bound<'py, PyAny> = compile_models.getattr("CompilerDiagnostic")?; + let compiler_diagnostic: Bound<'py, PyAny> = registry::class(py, RecordId::CompilerDiagnostic)?; let mut diagnostics: Vec> = Vec::with_capacity(report.diagnostics.len()); for row in &report.diagnostics { let fields: Bound<'py, PyDict> = PyDict::new(py); @@ -350,7 +340,7 @@ fn python_report<'py>( diagnostics.push(compiler_diagnostic.call((), Some(&fields))?); } let literal_relation: Bound<'py, PyAny> = - shape_class(reference_models.as_any(), "LiteralSqlRelation")?; + registry::class(reference_models.py(), RecordId::LiteralSqlRelation)?; let relation_name: Bound<'py, PyAny> = reference_models.getattr("RelationName")?; let mut unmatched: Vec> = Vec::with_capacity(report.unmatched.len()); for row in &report.unmatched { @@ -371,7 +361,7 @@ fn python_report<'py>( let fields: Bound<'py, PyDict> = PyDict::new(py); fields.set_item("diagnostics", PyTuple::new(py, diagnostics)?)?; fields.set_item("unmatched", PyTuple::new(py, unmatched)?)?; - shape_class(compile_models.as_any(), "PythonSqlReferenceReport")?.call((), Some(&fields)) + registry::class(py, RecordId::PythonSqlReferenceReport)?.call((), Some(&fields)) } /// `(method, sql, line)` for each literal SQL call to the context in a dedented function source. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/analysis/reference_facts.rs b/crates/sqlbuild-python/src/bindings/_helpers/analysis/reference_facts.rs index 81e054e539..85006788c8 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/analysis/reference_facts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/analysis/reference_facts.rs @@ -1,5 +1,7 @@ //! The explicit-reference facts of a compiled project, read from its Python views. +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyBool, PyList, PyTuple}; use sqlbuild_analysis::explicit_references::models::{ @@ -8,9 +10,6 @@ use sqlbuild_analysis::explicit_references::models::{ }; use sqlbuild_analysis::explicit_references::types::ResourceKey; -const DISCOVERY_MODELS_MODULE: &str = "sqlbuild._native"; -const PYTHON_HOOK_ENTRY: &str = "PythonHookEntry"; -const SQL_HOOK_ENTRY: &str = "SqlHookEntry"; const MODEL_HOOK_KEYS: [&str; 2] = ["pre_hooks", "post_hooks"]; const MIGRATE_FROM_KEY: &str = "migrate_from"; const OLD_NAME_VIEW_KEY: &str = "old_name_view"; @@ -107,10 +106,9 @@ struct HookEntries<'py> { impl<'py> HookEntries<'py> { fn imported(py: Python<'py>) -> PyResult { - let module = py.import(DISCOVERY_MODELS_MODULE)?; Ok(Self { - python: module.getattr(PYTHON_HOOK_ENTRY)?, - sql: module.getattr(SQL_HOOK_ENTRY)?, + python: registry::class(py, RecordId::PythonHookEntry)?, + sql: registry::class(py, RecordId::SqlHookEntry)?, }) } } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/analysis/required_columns.rs b/crates/sqlbuild-python/src/bindings/_helpers/analysis/required_columns.rs index 99bd354073..6a4870fc49 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/analysis/required_columns.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/analysis/required_columns.rs @@ -9,11 +9,11 @@ use sqlbuild_analysis::required_columns::main::resolve_required_columns::resolve use sqlbuild_analysis::required_columns::models::{ColumnRead, ReferenceInput}; use crate::bindings::_helpers::boundary::panics::value_error; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::types::CompilerDetach; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; - /// CTE-aware column reads of `query_sql`, resolved to the external resources it references. #[pyfunction] #[pyo3(signature = (*, query_sql, references, dialect))] @@ -40,7 +40,7 @@ fn analyze_resolved_column_reads<'py>( } RequiredColumnsError::Normalization(message) => value_error(message), })?; - let fact: Bound<'py, PyAny> = imported(py, COMPILE_MODELS_MODULE, "CompiledLineageSourceFact")?; + let fact: Bound<'py, PyAny> = registry::class(py, RecordId::CompiledLineageSourceFact)?; let mut facts: Vec> = Vec::with_capacity(reads.len()); for read in reads { facts.push(call_with( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/analysis/schema_request.rs b/crates/sqlbuild-python/src/bindings/_helpers/analysis/schema_request.rs index cd70197f7c..13d49e2f2b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/analysis/schema_request.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/analysis/schema_request.rs @@ -8,10 +8,10 @@ use sqlbuild_analysis::semantic_validation::main::normalize::normalize_analysis_ use sqlbuild_analysis::semantic_validation::models::NormalizationInput; use crate::bindings::_helpers::boundary::panics::value_error; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::types::CompilerDetach; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - const GENERIC_DIALECT: &str = "generic"; /// The query, relation schemas and binding context of one schema-validation request. @@ -61,7 +61,5 @@ pub(crate) fn schema_validation_request<'py>( parts.quoted_identifiers_ignore_case, )?; arguments.set_item("catalog", parts.catalog)?; - py.import(NATIVE_RECORDS_MODULE)? - .getattr("SqlSchemaValidationRequest")? - .call((), Some(&arguments)) + registry::class(py, RecordId::SqlSchemaValidationRequest)?.call((), Some(&arguments)) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/fixture_columns.rs b/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/fixture_columns.rs index 1c6dfec894..79386119f1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/fixture_columns.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/fixture_columns.rs @@ -14,12 +14,10 @@ use crate::bindings::_helpers::analysis_session::query_columns::ColumnsRow; use crate::bindings::_helpers::analysis_session::stage_functions::query_column_rows; use crate::bindings::_helpers::boundary::keyword_request::Request; use crate::bindings::_helpers::boundary::panics::compiler_error; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::types::CompilerDetach; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const ANALYSIS_MODELS_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const LINEAGE_TYPES_MODULE: &str = "sqlbuild.compiler.lineage.types"; const PROJECTION_FACTS_CONTEXT: &str = "native fixture projection facts"; const LEGACY_MODE: &str = "legacy"; @@ -38,10 +36,8 @@ fn infer_fixture_column_facts<'py>( query.set_item("sql", &query_sql)?; query.set_item("mode", LEGACY_MODE)?; query.set_item("recover_cte_facts", false)?; - let query: Bound<'py, PyAny> = py - .import(ANALYSIS_MODELS_MODULE)? - .getattr("NativeColumnQuery")? - .call((), Some(&query))?; + let query: Bound<'py, PyAny> = + registry::class(py, RecordId::NativeColumnQuery)?.call((), Some(&query))?; let empty: Bound<'py, PyAny> = PyDict::new(py).into_any(); let rows: Vec = query_column_rows( PyTuple::new(py, [query])?.as_any(), @@ -56,9 +52,7 @@ fn infer_fixture_column_facts<'py>( .compiler_detach(|| projection_facts(&query_sql, dialect.as_deref())) .map_err(|reason| compiler_error(native_failure(PROJECTION_FACTS_CONTEXT, &reason)))?; let simple_types: HashMap = facts.types.into_iter().collect(); - let column_class: Bound<'py, PyAny> = py - .import(COMPILE_MODELS_MODULE)? - .getattr("InferredColumn")?; + let column_class: Bound<'py, PyAny> = registry::class(py, RecordId::InferredColumn)?; let nullability_class: Bound<'py, PyAny> = py .import(LINEAGE_TYPES_MODULE)? .getattr("InferredNullability")?; @@ -80,9 +74,7 @@ fn infer_fixture_column_facts<'py>( PyFrozenSet::new(py, facts.null_literal_names)?, )?; arguments.set_item("quoted_names", PyFrozenSet::new(py, facts.quoted_names)?)?; - py.import(NATIVE_RECORDS_MODULE)? - .getattr("FixtureColumnInference")? - .call((), Some(&arguments)) + registry::class(py, RecordId::FixtureColumnInference)?.call((), Some(&arguments)) } pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/fixture_inferences.rs b/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/fixture_inferences.rs index 0db81c84a5..ff98e042bf 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/fixture_inferences.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/fixture_inferences.rs @@ -1,6 +1,8 @@ //! Fixture column inference computed once per fixture body and inference profile. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods}; use pyo3::{pyclass, pymethods}; @@ -31,7 +33,7 @@ impl FixtureColumnInferences { let py = query_sql.py(); let compute = || { call_with( - &imported(py, "sqlbuild._native", "infer_fixture_column_facts")?, + ®istry::native(py, NativeAttr::InferFixtureColumnFacts)?, &[ ("query_sql", query_sql.clone()), ("inference_profile", inference_profile.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/model_analyses.rs b/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/model_analyses.rs index 73b4c55997..0253298b28 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/model_analyses.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/model_analyses.rs @@ -13,13 +13,13 @@ use crate::bindings::_helpers::attachments::cursor_intrinsics::analysis_cursor_s use crate::bindings::_helpers::boundary::keyword_request::Request; use crate::bindings::_helpers::cache::native_store::current_code_identity; use crate::bindings::_helpers::compile_driver::analysis_cache::record_analysis_cache_metrics; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::semantic_shapes::shapes::{VALID_FROM_DEFAULT, VALID_TO_DEFAULT}; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ANALYSIS_MODELS_MODULE: &str = "sqlbuild._native"; -const PLANNER_TYPES_MODULE: &str = "sqlbuild._native"; const LINEAGE_TYPES_MODULE: &str = "sqlbuild.compiler.lineage.types"; -const LOG_DEBUG_MODULE: &str = "sqlbuild.diagnostics.main.log_debug_event"; const LOGGING_MODULE: &str = "logging"; const DEBUG_LOGGER: &str = "sqlbuild.compile"; const ANALYSIS_FAILURE_MESSAGE: &str = "native compact query analysis failed"; @@ -36,9 +36,7 @@ fn analyze_native_model_sql<'py>( let request = Request::new("analyze_native_model_sql", request)?; let py: Python<'py> = request.py(); let request: Bound<'py, PyAny> = request.get("request")?; - let analyses_class: Bound<'py, PyAny> = py - .import(ANALYSIS_MODELS_MODULE)? - .getattr("NativeModelAnalyses")?; + let analyses_class: Bound<'py, PyAny> = registry::class(py, RecordId::NativeModelAnalyses)?; let model_inputs: Bound<'py, PyAny> = request.getattr("model_inputs")?; if !model_inputs.is_truthy()? { let arguments: Bound<'py, PyDict> = PyDict::new(py); @@ -66,10 +64,13 @@ fn analyze_native_model_sql<'py>( ), None => (analysis_store(&request)?, py.None().into_bound(py)), }; - let session: Bound<'py, PyAny> = py - .import(NATIVE_MODULE)? - .getattr("start_model_analysis_session")? - .call1((catalog.getattr("native")?, session_request, cache, adapter))?; + let session: Bound<'py, PyAny> = registry::native(py, NativeAttr::StartModelAnalysisSession)? + .call1(( + catalog.getattr("native")?, + session_request, + cache, + adapter, + ))?; run_session(&request, (&python, &catalog), &session, &analyses_class) } @@ -130,10 +131,8 @@ fn analysis_store<'py>(request: &Bound<'py, PyAny>) -> PyResult = py - .import(NATIVE_MODULE)? - .getattr("content_digest")? - .call1((parts,))?; + let environment: Bound<'py, PyAny> = + registry::native(py, NativeAttr::ContentDigest)?.call1((parts,))?; Ok(PyTuple::new(py, [path, environment])?.into_any()) } @@ -270,11 +269,10 @@ fn model_rows<'py>( py: Python<'py>, model_inputs: &[Bound<'py, PyAny>], ) -> PyResult> { - let planner = py.import(PLANNER_TYPES_MODULE)?; - let enforced: Bound<'py, PyAny> = planner.getattr("ContractPolicy")?.getattr("ENFORCED")?; - let snapshot: Bound<'py, PyAny> = planner - .getattr("MaterializationType")? - .getattr("SNAPSHOT")?; + let enforced: Bound<'py, PyAny> = + registry::class(py, RecordId::ContractPolicy)?.getattr("ENFORCED")?; + let snapshot: Bound<'py, PyAny> = + registry::class(py, RecordId::MaterializationType)?.getattr("SNAPSHOT")?; let mut rows: Vec> = Vec::with_capacity(model_inputs.len()); for model_input in model_inputs { let values: Bound<'py, PyAny> = model_input.getattr("config")?.getattr("values")?; @@ -355,9 +353,7 @@ fn log_debug(py: Python<'_>, message: &str, failure: &Bound<'_, PyAny>) -> PyRes )?; arguments.set_item("message", message)?; arguments.set_item("sqlbuild_error", failure)?; - py.import(LOG_DEBUG_MODULE)? - .getattr("log_debug_event")? - .call((), Some(&arguments))?; + host::entry(py, ExecutorHost::LogDebugEvent)?.call((), Some(&arguments))?; Ok(()) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/outcome_objects.rs b/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/outcome_objects.rs index 9769df9baa..cd67a2362c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/outcome_objects.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/outcome_objects.rs @@ -1,6 +1,5 @@ //! Finished session outcomes and pivot proof rows as Python's analysis objects. -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class; use pyo3::IntoPyObjectExt; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; @@ -10,10 +9,9 @@ use crate::bindings::_helpers::boundary::keyword_request::Request; use crate::bindings::_helpers::record_behaviours::lineage_facts::{ confidence_member, transform_kind_member, }; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const LINEAGE_TYPES_MODULE: &str = "sqlbuild.compiler.lineage.types"; const LINEAGE_NATIVE: &str = "native"; @@ -53,7 +51,6 @@ fn session_outcome_analysis<'py>( .call_method1("outcome_rows", (request.get("position")?,))? .extract()?; let (succeeded, columns, source, rows, has_star, star_resolved, raw, validated, cleaned): OutcomeObjects<'py> = outcome.extract()?; - let models = py.import(COMPILE_MODELS_MODULE)?; let lineage: Bound<'py, PyAny> = if source != LINEAGE_NATIVE { lineage_facts(&rows)? } else if succeeded.is_truthy()? { @@ -61,9 +58,7 @@ fn session_outcome_analysis<'py>( } else { PyTuple::empty(py).into_any() }; - let binding_class: Bound<'py, PyAny> = py - .import(NATIVE_RECORDS_MODULE)? - .getattr("SqlBindingDiagnostic")?; + let binding_class: Bound<'py, PyAny> = registry::class(py, RecordId::SqlBindingDiagnostic)?; let mut bindings: Vec> = Vec::new(); for row in raw.try_iter()? { let row: Bound<'py, PyTuple> = @@ -71,7 +66,7 @@ fn session_outcome_analysis<'py>( bindings.push(binding_class.call1(row)?); } let result: Bound<'py, PyAny> = call( - &shape_class(models.as_any(), "PolyglotAnalysisResult")?, + ®istry::class(py, RecordId::PolyglotAnalysisResult)?, &[ ("analysis_succeeded", succeeded), ("columns", inferred_columns(&columns)?), @@ -86,7 +81,7 @@ fn session_outcome_analysis<'py>( ], )?; call( - &shape_class(models.as_any(), "ModelSqlAnalysis")?, + ®istry::class(py, RecordId::ModelSqlAnalysis)?, &[ ("polyglot_analysis", result), ("placeholders", request.get("placeholders")?), @@ -110,8 +105,7 @@ fn proof<'py>(row: &Bound<'py, PyAny>) -> PyResult> { } let (output_proven, columns, families, inputs, failure_reason, bare): ProofObjects<'py> = row.extract()?; - let models = py.import(COMPILE_MODELS_MODULE)?; - let family_class: Bound<'py, PyAny> = shape_class(models.as_any(), "DynamicColumnFamilyProof")?; + let family_class: Bound<'py, PyAny> = registry::class(py, RecordId::DynamicColumnFamilyProof)?; let mut proofs: Vec> = Vec::new(); for family in families.try_iter()? { let (name, inferred_type): (Bound<'py, PyAny>, Bound<'py, PyAny>) = family?.extract()?; @@ -127,7 +121,7 @@ fn proof<'py>(row: &Bound<'py, PyAny>) -> PyResult> { PyTuple::empty(py).into_any() }; call( - &shape_class(models.as_any(), "DynamicColumnContractProof")?, + ®istry::class(py, RecordId::DynamicColumnContractProof)?, &[ ("output_proven", output_proven), ("fixed_columns", fixed), @@ -145,9 +139,7 @@ fn inferred_columns<'py>(rows: &Bound<'py, PyAny>) -> PyResult if rows.is_none() { return Ok(rows.clone()); } - let column_class: Bound<'py, PyAny> = py - .import(COMPILE_MODELS_MODULE)? - .getattr("InferredColumn")?; + let column_class: Bound<'py, PyAny> = registry::class(py, RecordId::InferredColumn)?; let nullability_class: Bound<'py, PyAny> = py .import(LINEAGE_TYPES_MODULE)? .getattr("InferredNullability")?; @@ -220,8 +212,7 @@ fn compact_lineage<'py>(rows: &Bound<'py, PyAny>) -> PyResult> } let keys: Bound<'py, PyAny> = PyTuple::new(py, pool.keys())?.into_any(); call( - &py.import(COMPILE_MODELS_MODULE)? - .getattr("CompactLineageFacts")?, + ®istry::class(py, RecordId::CompactLineageFacts)?, &[ ("string_pool", keys), ("rows", PyTuple::new(py, compact)?.into_any()), @@ -232,10 +223,8 @@ fn compact_lineage<'py>(rows: &Bound<'py, PyAny>) -> PyResult> /// Native lineage rows as the plain facts Python's re-analysis returns. fn lineage_facts<'py>(rows: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = rows.py(); - let models = py.import(COMPILE_MODELS_MODULE)?; - let column_class: Bound<'py, PyAny> = - shape_class(models.as_any(), "CompiledLineageColumnFact")?; - let source_class: Bound<'py, PyAny> = models.getattr("CompiledLineageSourceFact")?; + let column_class: Bound<'py, PyAny> = registry::class(py, RecordId::CompiledLineageColumnFact)?; + let source_class: Bound<'py, PyAny> = registry::class(py, RecordId::CompiledLineageSourceFact)?; let mut facts: Vec> = Vec::new(); for row in rows.try_iter()? { let (output_column, transform_code, confidence_code, sources): ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/profile_inputs.rs b/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/profile_inputs.rs index ec8c4e31ba..2115c45c4f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/profile_inputs.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/profile_inputs.rs @@ -7,27 +7,20 @@ use sqlbuild_analysis::assembly::analysis_session::types::{Pairs, Shapes}; use crate::bindings::_helpers::analysis::binding_catalog::{ BindingCatalog, CASE_SENSITIVE_BINDING_DIALECTS, NATIVE_DIALECT_ALIASES, }; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; const LINEAGE_TYPES_MODULE: &str = "sqlbuild.compiler.lineage.types"; const GENERIC_DIALECT: &str = "generic"; const ADAPTER_RULE_ID: &str = "python"; -/// `(module, function, native rule id)` of the nullability rules the native analysis knows. -const KNOWN_RULES: [(&str, &str, &str); 3] = [ +/// The nullability rules the native analysis knows, with their native rule ids. +const KNOWN_RULES: [(AdapterHost, &str); 3] = [ + (AdapterHost::FirstArgNullability, "first_arg"), ( - "sqlbuild.adapter.type_system.main.first_arg_nullability", - "first_arg_nullability", - "first_arg", - ), - ( - "sqlbuild.adapter.type_system.main.conditional_result_nullability", - "conditional_result_nullability", + AdapterHost::ConditionalResultNullability, "conditional_result", ), - ( - "sqlbuild.adapter.type_system.main.safe_cast_nullability", - "safe_cast_nullability", - "safe_cast", - ), + (AdapterHost::SafeCastNullability, "safe_cast"), ]; /// The adapter's own nullability rules and `InferredNullability`, which native analysis calls. @@ -37,8 +30,8 @@ pub(crate) type AdapterRules = Option<(Py, Py)>; pub(crate) fn nullability_rules(profile: &Bound<'_, PyAny>) -> PyResult<(Pairs, AdapterRules)> { let py: Python<'_> = profile.py(); let known: Bound<'_, PyDict> = PyDict::new(py); - for (module, function, id) in KNOWN_RULES { - known.set_item(py.import(module)?.getattr(function)?, id)?; + for (rule, id) in KNOWN_RULES { + known.set_item(host::entry(py, rule)?, id)?; } let adapter: Bound<'_, PyDict> = PyDict::new(py); let mut rows: Pairs = Vec::new(); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/stage_functions.rs b/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/stage_functions.rs index 3635f6b608..ba26c4d945 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/stage_functions.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/stage_functions.rs @@ -20,6 +20,8 @@ use crate::bindings::_helpers::analysis_session::session::{ infer_expression_source_shapes, prove_dynamic_column_contracts, }; use crate::bindings::_helpers::boundary::keyword_request::Request; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::models::ProjectCatalog; const BATCH_MODE: &str = "batch"; @@ -32,8 +34,6 @@ const LINEAGE_RESOURCE_TYPES: [(&str, &str); 4] = [ ("seed", "seed"), ("table_fn", "table_fn"), ]; -const ANALYSIS_MODELS_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const LINEAGE_TYPES_MODULE: &str = "sqlbuild.compiler.lineage.types"; /// Each query's `(succeeded, column rows, has star)`; a native internal failure raises. @@ -70,12 +70,8 @@ fn infer_native_query_columns<'py>( &request.get("profile")?, (&types, &nullability), )?; - let query_columns: Bound<'py, PyAny> = py - .import(ANALYSIS_MODELS_MODULE)? - .getattr("NativeQueryColumns")?; - let column_class: Bound<'py, PyAny> = py - .import(COMPILE_MODELS_MODULE)? - .getattr("InferredColumn")?; + let query_columns: Bound<'py, PyAny> = registry::class(py, RecordId::NativeQueryColumns)?; + let column_class: Bound<'py, PyAny> = registry::class(py, RecordId::InferredColumn)?; let nullability_class: Bound<'py, PyAny> = py .import(LINEAGE_TYPES_MODULE)? .getattr("InferredNullability")?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/attachments/cursor_intrinsics.rs b/crates/sqlbuild-python/src/bindings/_helpers/attachments/cursor_intrinsics.rs index 262ad5285a..158acd1818 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/attachments/cursor_intrinsics.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/attachments/cursor_intrinsics.rs @@ -8,9 +8,9 @@ use sqlbuild_attachments::cursor_intrinsics::main::validated_model_intrinsics::v use sqlbuild_attachments::cursor_intrinsics::models::IntrinsicModel; use crate::bindings::_helpers::boundary::panics::compiler_guard; -use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; const INCREMENTAL_MATERIALIZATION: &str = "incremental"; const CURSOR_START: &str = "__cursor_start"; const CURSOR_START_CALL: &str = "__cursor_start()"; @@ -37,7 +37,7 @@ pub(crate) fn reserved_cursor_markers() -> Vec { /// A `CompileInputError` carrying `message`. pub(crate) fn compile_input_error(py: Python<'_>, message: String) -> PyResult { Ok(PyErr::from_value( - imported(py, COMPILE_ERRORS_MODULE, "CompileInputError")?.call1((message,))?, + registry::error(py, ErrorId::CompileInput)?.call1((message,))?, )) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/attachments/reference_markers.rs b/crates/sqlbuild-python/src/bindings/_helpers/attachments/reference_markers.rs index 849c6f09cf..f259791419 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/attachments/reference_markers.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/attachments/reference_markers.rs @@ -8,8 +8,9 @@ use sqlbuild_attachments::reference_markers::main::unresolved_reference_marker:: use sqlbuild_attachments::sql_lexing::main::unclosed_message::unclosed_message; use crate::bindings::_helpers::boundary::panics::compiler_guard; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; /// The reference function and diagnostic code of each marker kind, in the scanner's order. const MARKER_KINDS: [(&str, &str); 5] = [ ("__ref", "R001"), @@ -45,9 +46,7 @@ fn assert_no_unresolved_sql_markers( })?; match outcome { (_, Some(unclosed)) => Err(PyErr::from_value( - py.import(COMPILE_ERRORS_MODULE)? - .getattr("CompileInputError")? - .call1((unclosed,))?, + registry::error(py, ErrorId::CompileInput)?.call1((unclosed,))?, )), (Some(marker), None) => { let (function, code) = MARKER_KINDS.get(marker).copied().ok_or_else(|| { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/audit_command/command.rs b/crates/sqlbuild-python/src/bindings/_helpers/audit_command/command.rs index b70e9eaabe..1dc77c5478 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/audit_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/audit_command/command.rs @@ -12,17 +12,12 @@ use sqlbuild_core::audit_command::models::AuditCounts; use sqlbuild_core::cli_text::models::CliStyle; use crate::bindings::_helpers::command_runtime::invocation::resolve_reported_command_invocation; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, within}; +use crate::bindings::_helpers::compile_driver::python_calls::{call_with, within}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const EXTERNAL_REFS_MODULE: &str = "sqlbuild._native"; -const STATIC_COMMAND_MODULE: &str = "sqlbuild._native"; -const AUDIT_PLAN_MODULE: &str = "sqlbuild._native"; -const COST_CONTEXT_MODULE: &str = "sqlbuild._native"; -const EXECUTOR_RUN_MODULE: &str = "sqlbuild.executor.pipeline.main.run"; -const PROJECTION_MODULE: &str = "sqlbuild.executor.auditing.main._current_audit_result_projection"; const EXECUTION_COMMAND: &str = "sqb audit"; const PASS_OUTCOME: &str = "pass"; const WARN_OUTCOME: &str = "warn"; @@ -109,18 +104,14 @@ fn compile_audits<'py>( let discovered: Bound<'py, PyAny> = invocation.getattr("discovered_inputs")?; let adapter: Bound<'py, PyAny> = invocation.getattr("adapter")?; let resolver: Bound<'py, PyAny> = call_with( - &imported( - py, - EXTERNAL_REFS_MODULE, - "resolve_external_sql_reference_resolver", - )?, + ®istry::native(py, NativeAttr::ResolveExternalSqlReferenceResolver)?, &[ ("project_dir", invocation.getattr("effective_project_dir")?), ("discovered_inputs", discovered.clone()), ], )?; let options: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "CompilePipelineOptions")?, + ®istry::class(py, RecordId::CompilePipelineOptions)?, &[ ("selected_target", request.getattr("selected_target")?), ("no_sql_validation", request.getattr("no_sql_validation")?), @@ -136,7 +127,7 @@ fn compile_audits<'py>( ], )?; let context: Bound<'py, PyAny> = call_with( - &imported(py, STATIC_COMMAND_MODULE, "compile_static_command_context")?, + ®istry::native(py, NativeAttr::CompileStaticCommandContext)?, &[ ("discovered_inputs", discovered), ("adapter", adapter.clone()), @@ -150,7 +141,7 @@ fn compile_audits<'py>( ], )?; let plan_output: Bound<'py, PyAny> = call_with( - &imported(py, AUDIT_PLAN_MODULE, "build_audit_command_plan")?, + ®istry::native(py, NativeAttr::BuildAuditCommandPlan)?, &[ ("project", context.getattr("project")?), ("adapter", adapter), @@ -159,7 +150,7 @@ fn compile_audits<'py>( ], )?; call_with( - &imported(py, NATIVE_MODULE, "build_static_pipeline_result")?, + ®istry::native(py, NativeAttr::BuildStaticPipelineResult)?, &[("context", context), ("plan_output", plan_output)], ) } @@ -195,7 +186,7 @@ fn prepare_run<'py>( let stream: Bound<'py, PyAny> = invocation.getattr("progress_stream")?; let use_color: Bound<'py, PyAny> = invocation.getattr("use_color")?; let progress: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "AuditProgressReporter")?, + ®istry::native(py, NativeAttr::AuditProgressReporter)?, &[ ("entries", entries.clone()), ("worker_limit", PyInt::new(py, worker_count).into_any()), @@ -204,7 +195,7 @@ fn prepare_run<'py>( ], )?; let projector: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "current_native_progress_projector")?.call0()?; + registry::native(py, NativeAttr::CurrentNativeProgressProjector)?.call0()?; if !projector.is_none() { for entry in entries.try_iter()? { call_with( @@ -214,7 +205,7 @@ fn prepare_run<'py>( } } let connection: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "ConnectionProgressReporter")?, + ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, &[ ("adapter_name", invocation.getattr("adapter_name")?), ("stream", stream), @@ -245,7 +236,7 @@ fn write_headers( header.set_item("target", py.None())?; header.set_item("concurrency", concurrency)?; header.set_item("use_color", &use_color)?; - imported(py, NATIVE_MODULE, "write_execution_header")?.call((), Some(&header))?; + registry::native(py, NativeAttr::WriteExecutionHeader)?.call((), Some(&header))?; let entries: Bound<'_, PyAny> = pipeline.getattr("plan_output")?.getattr("audit_entries")?; let mut models: BTreeSet = BTreeSet::new(); for entry in entries.try_iter()? { @@ -275,7 +266,7 @@ fn execute<'py>( let plan: Bound<'py, PyAny> = pipeline.getattr("plan_output")?; let project: Bound<'py, PyAny> = pipeline.getattr("project")?; let event_writer: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "ExecutionEventWriter")?.call0()?; + registry::native(py, NativeAttr::ExecutionEventWriter)?.call0()?; let relay: Bound<'py, AuditCallbackRelay> = Bound::new( py, AuditCallbackRelay { @@ -288,7 +279,7 @@ fn execute<'py>( run.progress .call_method1("set_result_callback", (relay.getattr("on_result")?,))?; let projector: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "current_native_progress_projector")?.call0()?; + registry::native(py, NativeAttr::CurrentNativeProgressProjector)?.call0()?; if !projector.is_none() { let ordinals: Bound<'py, PyDict> = PyDict::new(py); let mut total: usize = 0; @@ -312,7 +303,7 @@ fn execute<'py>( .call_method1("__truediv__", (&run_id,))? .call_method1("__truediv__", ("statements.jsonl",))?; let scope: Bound<'py, PyAny> = call_with( - &imported(py, COST_CONTEXT_MODULE, "CostContext")?.getattr("scope")?, + ®istry::native(py, NativeAttr::CostContext)?.getattr("scope")?, &[ ("run_id", run_id.clone()), ("resource_type", text(py, "run")), @@ -323,7 +314,7 @@ fn execute<'py>( )?; within(&scope, |_| { let callbacks: Bound<'py, PyAny> = call_with( - &imported(py, EXECUTOR_RUN_MODULE, "AuditPipelineCallbacks")?, + &host::entry(py, ExecutorHost::AuditPipelineCallbacks)?, &[ ( "on_connection_start", @@ -347,7 +338,7 @@ fn execute<'py>( ], )?; call_with( - &imported(py, EXECUTOR_RUN_MODULE, "run_audit_pipeline_with_callbacks")?, + &host::entry(py, ExecutorHost::RunAuditPipelineWithCallbacks)?, &[ ("plan", plan.clone()), ( @@ -390,7 +381,7 @@ fn audit_payload<'py>( ) -> PyResult> { let py: Python<'py> = results.py(); let payload: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "format_audit_execution_json")?, + ®istry::native(py, NativeAttr::FormatAuditExecutionJson)?, &[ ("results", results.clone()), ( @@ -401,7 +392,7 @@ fn audit_payload<'py>( ], )?; let projection: Bound<'py, PyAny> = - imported(py, PROJECTION_MODULE, "current_audit_result_projection")?.call0()?; + host::entry(py, ExecutorHost::CurrentAuditResultProjection)?.call0()?; if projection.is_none() { return Ok(payload); } @@ -468,7 +459,7 @@ fn complete( (run.concurrency, run.worker_count), )?; call_with( - &imported(py, NATIVE_MODULE, "write_execution_json_output")?, + ®istry::native(py, NativeAttr::WriteExecutionJsonOutput)?, &[ ("payload", payload), ("json_output", request.getattr("json_output")?), @@ -484,7 +475,7 @@ fn complete( fn run_audit_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { let invocation: Bound<'_, PyAny> = resolve_reported_command_invocation( request, - &imported(py, NATIVE_MODULE, "AuditInvocation")?, + ®istry::class(py, RecordId::AuditInvocation)?, true, )?; let pipeline: Bound<'_, PyAny> = compile_audits(request, &invocation)?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/auditing/identity.rs b/crates/sqlbuild-python/src/bindings/_helpers/auditing/identity.rs index 2bdbaebbfb..c1c762345f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/auditing/identity.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/auditing/identity.rs @@ -1,5 +1,7 @@ //! Audit gate identities of planned audits, built as `AuditGateIdentity` objects. +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; use pyo3::{IntoPyObject, pyfunction, wrap_pyfunction}; @@ -8,7 +10,6 @@ use sqlbuild_analysis::auditing::models::{ GateIdentity, MeasurementFacts, PlannedAudit, ThresholdBound, Thresholds, }; -const AUDITING_MODELS_MODULE: &str = "sqlbuild._native"; const MEASUREMENT_MODE: &str = "measurement"; fn enum_value(value: &Bound<'_, PyAny>) -> PyResult { @@ -92,8 +93,7 @@ fn build_audit_gate_identity<'py>( let entries: Vec> = audits.try_iter()?.collect::>()?; let planned: Vec = entries.iter().map(planned_audit).collect::>()?; let gate: GateIdentity = audit_gate_identity(&planned); - let models: Bound<'py, PyModule> = py.import(AUDITING_MODELS_MODULE)?; - let identity_type: Bound<'py, PyAny> = models.getattr("AuditIdentity")?; + let identity_type: Bound<'py, PyAny> = registry::class(py, RecordId::AuditIdentity)?; let mut identities: Vec> = Vec::with_capacity(gate.audits.len()); for identity in gate.audits { let entry: &Bound<'py, PyAny> = &entries[identity.index]; @@ -138,7 +138,7 @@ fn build_audit_gate_identity<'py>( )?); } keyword_call( - &models.getattr("AuditGateIdentity")?, + ®istry::class(py, RecordId::AuditGateIdentity)?, vec![ ( "binding_set_hash", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/auditing/measurement.rs b/crates/sqlbuild-python/src/bindings/_helpers/auditing/measurement.rs index 30abd51eec..cd764d371c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/auditing/measurement.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/auditing/measurement.rs @@ -8,16 +8,13 @@ use sqlbuild_analysis::auditing::main::evaluate_measurement::evaluate_measuremen use sqlbuild_analysis::auditing::models::{MeasurementOutcome, Thresholds}; use crate::bindings::_helpers::auditing::identity::thresholds as read_thresholds; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; const AUDITING_TYPES_MODULE: &str = "sqlbuild.compiler.auditing.types"; -const AUDITING_ERRORS_MODULE: &str = "sqlbuild._native"; fn measurement_error(py: Python<'_>, message: &str) -> PyErr { - match py - .import(AUDITING_ERRORS_MODULE) - .and_then(|module| module.getattr("MeasurementAuditError")) - .and_then(|class| class.call1((message,))) - { + match registry::error(py, ErrorId::MeasurementAudit).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/auditing/schema_audits.rs b/crates/sqlbuild-python/src/bindings/_helpers/auditing/schema_audits.rs index a139607174..0504011309 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/auditing/schema_audits.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/auditing/schema_audits.rs @@ -13,8 +13,8 @@ use crate::bindings::_helpers::boundary::keyword_request::Request; use crate::bindings::_helpers::model_config::authored_nodes::PyNode; use crate::bindings::_helpers::model_config::contract_classes::contract_classes; use crate::bindings::_helpers::model_config::header_metadata::{ContractClasses, audit_tuple}; - -const IDENTITY_ERRORS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; /// A resource identity error as `ResourceIdentityError`, anything else as `error_class(message)`. fn raised(py: Python<'_>, error: ConfigError, error_class: &Bound<'_, PyAny>) -> PyResult { @@ -24,8 +24,7 @@ fn raised(py: Python<'_>, error: ConfigError, error_class: &Bound<'_, PyAny>) -> if let Some(help) = error.help { keywords.set_item("help", help)?; } - py.import(IDENTITY_ERRORS_MODULE)? - .getattr("ResourceIdentityError")? + registry::error(py, ErrorId::ResourceIdentity)? } _ => error_class.clone(), }; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/authored_values/python_nodes.rs b/crates/sqlbuild-python/src/bindings/_helpers/authored_values/python_nodes.rs index 9a09f3ab30..55bdbb7ed1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/authored_values/python_nodes.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/authored_values/python_nodes.rs @@ -13,11 +13,11 @@ use sqlbuild_core::identifier_text::main::is_lowercase_identifier::is_lowercase_ use crate::bindings::_helpers::boundary::keyword_request::Request; use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; const ERRORS_MODULE: &str = "sqlbuild.errors.contracts.exceptions"; const SHARED_INPUT_ERROR: &str = "SharedInputError"; -const MODELS_MODULE: &str = "sqlbuild.python_nodes.models"; -const RESOURCE_REF_CLASS: &str = "SqlResourceRef"; const RANDOM_MODULE: &str = "random"; const MAX_ATTEMPTS_TOO_LOW: &str = "RetryPolicy max_attempts must be at least 1"; const INITIAL_DELAY_NEGATIVE: &str = "RetryPolicy initial_delay_seconds must be non-negative"; @@ -177,7 +177,7 @@ fn validate_audit_case( #[pyfunction] fn normalize_hook_reads<'py>(value: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = value.py(); - let reference: Bound<'py, PyAny> = imported(py, MODELS_MODULE, RESOURCE_REF_CLASS)?; + let reference: Bound<'py, PyAny> = host::entry(py, UserCodeHost::SqlResourceRef)?; let reads: Vec> = if value.is_instance(&reference)? { vec![value.clone()] } else { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/build_command/command.rs b/crates/sqlbuild-python/src/bindings/_helpers/build_command/command.rs index 9069c440bf..cdcbc0c17a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/command.rs @@ -15,17 +15,17 @@ use crate::bindings::_helpers::build_command::planning::{ compile_build_plan, enforce_build_plan_policies, run_defer_clone_prephase, write_compile_target, write_plan_text, }; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, within}; - -const TIMING_TRACKER_MODULE: &str = "sqlbuild._native"; -const RESOURCE_REPORTING_MODULE: &str = "sqlbuild.diagnostics.main.process_resource_reporting"; -const DEADLINE_MODULE: &str = "sqlbuild.runtime.execution_limits.main.execution_deadline_scope"; -const PROVIDER_SESSION_MODULE: &str = "sqlbuild.provider.main.session"; +use crate::bindings::_helpers::compile_driver::python_calls::{call_with, within}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; fn run_with_providers(context: &BuildContext<'_>) -> PyResult { let py: Python<'_> = context.py(); let session: Bound<'_, PyAny> = call_with( - &imported(py, PROVIDER_SESSION_MODULE, "build_provider_session")?, + &host::entry(py, UserCodeHost::BuildProviderSession)?, &[( "discovered_providers", context.get("discovered_inputs")?.getattr("providers")?, @@ -71,7 +71,7 @@ fn run_within_limits( let context: BuildContext<'_> = resolve_build_context(request, tracker, started)?; let limits: Bound<'_, PyAny> = context.get("execution_limits")?; let deadline: Bound<'_, PyAny> = call_with( - &imported(py, DEADLINE_MODULE, "execution_deadline_scope")?, + &host::entry(py, ExecutorHost::ExecutionDeadlineScope)?, &[ ( "max_duration_seconds", @@ -90,9 +90,9 @@ fn run_within_limits( #[pyo3(signature = (*, request))] fn run_build_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { let tracker: Bound<'_, PyAny> = - imported(py, TIMING_TRACKER_MODULE, "BuildPhaseTimingTracker")?.call0()?; + registry::native(py, NativeAttr::BuildPhaseTimingTracker)?.call0()?; let reporting: Bound<'_, PyAny> = call_with( - &imported(py, RESOURCE_REPORTING_MODULE, "process_resource_reporting")?, + &host::entry(py, ExecutorHost::ProcessResourceReporting)?, &[("enabled", request.getattr("debug")?)], )?; within(&reporting, |_| { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/build_command/completion.rs b/crates/sqlbuild-python/src/bindings/_helpers/build_command/completion.rs index 2800f33c32..a93c335b2f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/completion.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/completion.rs @@ -17,18 +17,18 @@ use crate::bindings::_helpers::build_command::invocation::BuildContext; use crate::bindings::_helpers::build_command::support::{ boolean, emit, joined, none, text, utc_now, }; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::compile_outputs::runtime_target::{ write_python_check_runtime_target, write_runtime_target, }; use crate::bindings::_helpers::cost_report::degradation::{ finalize_build_cost as finalize_cost, render_build_cost, }; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const COST_TYPES: &str = "sqlbuild.cost.types"; -const BUILD_MODELS: &str = "sqlbuild.executor.build.models"; -const BUILD_TYPES: &str = "sqlbuild.executor.build.types"; const SUCCESS_STATUS: &str = "success"; const PENDING_MESSAGE: &str = "Build cost collection has not completed."; const INTERRUPTED_MESSAGE: &str = "Build was interrupted before cost collection completed."; @@ -75,14 +75,14 @@ pub(crate) fn finalize_build_cost<'py>( ]; fields.extend(cost.overrides); let finalization: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "BuildCostFinalization")?, + ®istry::class(py, RecordId::BuildCostFinalization)?, &fields, )?; finalize_cost(&finalization) } fn cost_status<'py>(py: Python<'py>, name: &str) -> PyResult> { - imported(py, COST_TYPES, "CostStatus")?.getattr(name) + host::entry(py, ExecutorHost::CostStatus)?.getattr(name) } /// Record a pending cost state before execution starts. @@ -154,7 +154,7 @@ fn cost_payload<'py>(py: Python<'py>, record: &Bound<'py, PyAny>) -> PyResult( fn write_json(context: &BuildContext<'_>, payload: Bound<'_, PyAny>) -> PyResult<()> { call_with( - &imported(context.py(), NATIVE_MODULE, "write_execution_json_output")?, + ®istry::native(context.py(), NativeAttr::WriteExecutionJsonOutput)?, &[ ("payload", payload), ("json_output", context.request.getattr("json_output")?), @@ -189,7 +189,7 @@ fn finalize_no_work(context: &BuildContext<'_>, pipeline: &Bound<'_, PyAny>) -> let py: Python<'_> = context.py(); let plan: Bound<'_, PyAny> = pipeline.getattr("plan_output")?; if call_with( - &imported(py, NATIVE_MODULE, "plan_has_executable_work")?, + ®istry::native(py, NativeAttr::PlanHasExecutableWork)?, &[ ("plan", plan.clone()), ( @@ -220,14 +220,14 @@ fn finalize_no_work(context: &BuildContext<'_>, pipeline: &Bound<'_, PyAny>) -> }, )?; let result: Bound<'_, PyAny> = call_with( - &imported(py, BUILD_MODELS, "BuildExecutionResult")?, + &host::entry(py, ExecutorHost::BuildExecutionResult)?, &[( "status", - imported(py, BUILD_TYPES, "BuildStatus")?.getattr("SUCCESS")?, + host::entry(py, ExecutorHost::BuildStatus)?.getattr("SUCCESS")?, )], )?; let payload: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "format_build_execution_json")?, + ®istry::native(py, NativeAttr::FormatBuildExecutionJson)?, &[ ("result", result), ("plan", plan), @@ -347,7 +347,7 @@ pub(crate) fn complete_direct_build<'py>( let cost_seconds: f64 = finish_cost_timing(context, cost_started); let record: Bound<'py, PyAny> = record?; let footer: String = call_with( - &imported(py, NATIVE_MODULE, "format_build_footer")?, + ®istry::native(py, NativeAttr::FormatBuildFooter)?, &[ ("result", result.clone()), ("elapsed", preparation.callbacks.getattr("elapsed")?), @@ -358,7 +358,7 @@ pub(crate) fn complete_direct_build<'py>( .extract()?; emit(&context.stream, &format!("\n{footer}\n"))?; let payload: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "format_degradable_build_execution_json")?, + ®istry::native(py, NativeAttr::FormatDegradableBuildExecutionJson)?, &[ ("result", result.clone()), ("plan", plan), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/build_command/defer_clone.rs b/crates/sqlbuild-python/src/bindings/_helpers/build_command/defer_clone.rs index 7b183ca425..b67e8007ac 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/defer_clone.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/defer_clone.rs @@ -14,19 +14,11 @@ use sqlbuild_core::build_command::errors::BuildCommandError; use crate::bindings::_helpers::build_command::invocation::BuildContext; use crate::bindings::_helpers::build_command::policies::build_command_error; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const CLONE_MODELS: &str = "sqlbuild.executor.clone.models"; -const CLONE_TYPES: &str = "sqlbuild.executor.clone.types"; -const SPEC_TYPES: &str = "sqlbuild._native"; -const TRANSIENT_MODELS_MODULE: &str = - "sqlbuild.executor.clone.main._build_destination_transient_models"; -const RETENTION_REQUESTS_MODULE: &str = "sqlbuild.executor.clone.main._build_retention_requests"; -const EXECUTE_CLONE_MODULE: &str = "sqlbuild.executor.clone.main.execute"; -const FINGERPRINTING_MODULE: &str = "sqlbuild.executor.clone.main._fingerprinting"; -const CLONE_STREAM_MODULE: &str = "sqlbuild.executor.clone.main._run_prephase_clone_stream"; const VIEW_MATERIALIZATION: &str = "view"; const RECREATED_TYPES: [&str; 2] = ["udf", "table_fn"]; const CLONABLE_TYPES: [&str; 4] = ["model", "seed", "udf", "table_fn"]; @@ -107,12 +99,12 @@ fn membership<'py>( fn traversed_names<'py>( scope: &Bound<'py, PyAny>, - (module, function): (&str, &str), + function: NativeAttr, (is_clonable, is_view): (&Bound<'py, PyAny>, &Bound<'py, PyAny>), ) -> PyResult> { let py: Python<'py> = scope.py(); let keys: Bound<'py, PyAny> = call_with( - &imported(py, module, function)?, + ®istry::native(py, function)?, &[ ("selected", scope.getattr("selected_keys")?), ("upstream", scope.getattr("upstream_deps")?), @@ -162,12 +154,8 @@ fn defer_clone_selection(scope: &Bound<'_, PyAny>) -> PyResult<(Vec, Vec let is_view: Bound<'_, PyAny> = membership(py, &keys, |key| recreated_over_clones(scope, key))?; let callbacks: (&Bound<'_, PyAny>, &Bound<'_, PyAny>) = (&is_clonable, &is_view); Ok(( - traversed_names(scope, (NATIVE_MODULE, "resolve_clone_boundary"), callbacks)?, - traversed_names( - scope, - (NATIVE_MODULE, "resolve_skipped_view_chain"), - callbacks, - )?, + traversed_names(scope, NativeAttr::ResolveCloneBoundary, callbacks)?, + traversed_names(scope, NativeAttr::ResolveSkippedViewChain, callbacks)?, selected_executable_model_count(scope)?, )) } @@ -183,10 +171,10 @@ fn resolve_defer_clone_selection( fn native<'py>( py: Python<'py>, - name: &str, + name: NativeAttr, fields: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - call_with(&imported(py, NATIVE_MODULE, name)?, fields) + call_with(®istry::native(py, name)?, fields) } fn external_resolver<'py>( @@ -195,7 +183,7 @@ fn external_resolver<'py>( ) -> PyResult> { native( project_dir.py(), - "resolve_external_sql_reference_resolver", + NativeAttr::ResolveExternalSqlReferenceResolver, &[ ("project_dir", project_dir.clone()), ("discovered_inputs", discovered.clone()), @@ -232,7 +220,7 @@ fn clone_input<'py>( let destination_models: Bound<'py, PyAny> = pipeline.getattr("destination_model_entries")?; let target: Bound<'py, PyAny> = native( py, - "resolve_target_config", + NativeAttr::ResolveTargetConfig, &[ ( "project_config", @@ -247,11 +235,7 @@ fn clone_input<'py>( selected.push(entry?.getattr("name")?); } let retention: Bound<'py, PyAny> = call_with( - &imported( - py, - RETENTION_REQUESTS_MODULE, - "build_destination_retention_requests", - )?, + &host::entry(py, ExecutorHost::BuildDestinationRetentionRequests)?, &[ ("project", project.clone()), ("adapter_name", prephase.adapter.getattr("adapter_name")?), @@ -267,9 +251,9 @@ fn clone_input<'py>( )?; let allow_decrease: bool = target .getattr("time_travel_retention_decrease")? - .eq(imported(py, SPEC_TYPES, "RetentionDecreasePolicy")?.getattr("ALLOW")?)?; + .eq(registry::class(py, RecordId::RetentionDecreasePolicy)?.getattr("ALLOW")?)?; let sources: Bound<'py, PyAny> = call_with( - &imported(py, CLONE_MODELS, "CloneSourceEntries")?, + &host::entry(py, ExecutorHost::CloneSourceEntries)?, &[ ("origin", pipeline.getattr("origin_source_entries")?), ( @@ -279,7 +263,7 @@ fn clone_input<'py>( ], )?; call_with( - &imported(py, CLONE_MODELS, "CloneExecutionInput")?, + &host::entry(py, ExecutorHost::CloneExecutionInput)?, &[ ("source_entries", sources), ( @@ -316,11 +300,7 @@ fn clone_input<'py>( ( "destination_transient_models", call_with( - &imported( - py, - TRANSIENT_MODELS_MODULE, - "build_destination_transient_models", - )?, + &host::entry(py, ExecutorHost::BuildDestinationTransientModels)?, &[("project", project.clone())], )?, ), @@ -342,7 +322,7 @@ fn executed_clone<'py>( ) -> PyResult> { let py: Python<'py> = context.py(); let progress_stream: Bound<'py, PyAny> = context.invocation.getattr("progress_stream")?; - let execute: Bound<'py, PyAny> = imported(py, EXECUTE_CLONE_MODULE, "execute_clone")?; + let execute: Bound<'py, PyAny> = host::entry(py, ExecutorHost::ExecuteClone)?; if progress_stream.is_none() { let inputs: Bound<'py, PyAny> = clone_input( context, @@ -382,7 +362,7 @@ fn executed_clone<'py>( }, )?; call_with( - &imported(py, CLONE_STREAM_MODULE, "run_prephase_clone_stream")?, + &host::entry(py, ExecutorHost::RunPrephaseCloneStream)?, &[ ("stream", progress_stream), ("title", text(py, "defer clone")), @@ -395,7 +375,7 @@ fn executed_clone<'py>( fn failed_items(result: &Bound<'_, PyAny>) -> PyResult> { let py: Python<'_> = result.py(); - let status: Bound<'_, PyAny> = imported(py, CLONE_TYPES, "CloneStatus")?; + let status: Bound<'_, PyAny> = host::entry(py, ExecutorHost::CloneStatus)?; let (failed, warning) = (status.getattr("FAILED")?, status.getattr("WARNING")?); let mut items: Vec = Vec::new(); for item in result.getattr("item_results")?.try_iter()? { @@ -434,7 +414,7 @@ fn clone_boundaries<'py>( let request: &Bound<'py, PyAny> = &context.request; let pipeline: Bound<'py, PyAny> = native( py, - "run_clone_pipeline_with_options", + NativeAttr::RunClonePipelineWithOptions, &[ ("discovered_inputs", prephase.discovered.clone()), ("adapter", prephase.adapter.clone()), @@ -443,7 +423,7 @@ fn clone_boundaries<'py>( ( "destination_connection", call_with( - &imported(py, NATIVE_RECORDS_MODULE, "ClonePipelineConnection")?, + ®istry::class(py, RecordId::ClonePipelineConnection)?, &[ ("config", context.invocation.getattr("connection_config")?), ("handle", connection.clone()), @@ -453,7 +433,7 @@ fn clone_boundaries<'py>( ( "options", call_with( - &imported(py, NATIVE_RECORDS_MODULE, "ClonePipelineOptions")?, + ®istry::class(py, RecordId::ClonePipelineOptions)?, &[ ("no_sql_validation", request.getattr("no_sql_validation")?), ("no_cache", request.getattr("no_cache")?), @@ -489,7 +469,7 @@ fn clone_boundaries<'py>( } let project: Bound<'py, PyAny> = pipeline.getattr("destination_project")?; call_with( - &imported(py, FINGERPRINTING_MODULE, "copy_clone_fingerprints")?, + &host::entry(py, ExecutorHost::CopyCloneFingerprints)?, &[ ("result", result), ( @@ -571,7 +551,7 @@ pub(crate) fn run_defer_clone_boundary_prephase( let discovered: Bound<'_, PyAny> = invocation.getattr("discovered_inputs")?; let split: Bound<'_, PyAny> = native( py, - "split_unit_test_selectors", + NativeAttr::SplitUnitTestSelectors, &[ ("select", request.getattr("select")?), ("exclude", request.getattr("exclude")?), @@ -589,7 +569,7 @@ pub(crate) fn run_defer_clone_boundary_prephase( let project_dir: Bound<'_, PyAny> = invocation.getattr("effective_project_dir")?; let project: Bound<'_, PyAny> = native( py, - "build_compiled_project", + NativeAttr::BuildCompiledProject, &[ ("discovered_inputs", discovered.clone()), ("adapter", adapter.clone()), @@ -598,7 +578,7 @@ pub(crate) fn run_defer_clone_boundary_prephase( ( "analysis_selection", call_with( - &imported(py, NATIVE_RECORDS_MODULE, "CompileAnalysisSelection")?, + ®istry::class(py, RecordId::CompileAnalysisSelection)?, &[("no_cache", request.getattr("no_cache")?)], )?, ), @@ -611,7 +591,7 @@ pub(crate) fn run_defer_clone_boundary_prephase( )?; let scope: Bound<'_, PyAny> = native( py, - "build_planner_scope", + NativeAttr::BuildPlannerScope, &[ ("project", project.clone()), ("select", split.getattr("select")?), @@ -626,7 +606,7 @@ pub(crate) fn run_defer_clone_boundary_prephase( defer_clone_selection(&scope)?; native( py, - "enforce_model_execution_limit", + NativeAttr::EnforceModelExecutionLimit, &[ ("model_count", model_count.into_pyobject(py)?.into_any()), ("target_name", invocation.getattr("effective_target_name")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/build_command/execution.rs b/crates/sqlbuild-python/src/bindings/_helpers/build_command/execution.rs index b5e49386a6..9d96806187 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/execution.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/execution.rs @@ -18,19 +18,15 @@ use crate::bindings::_helpers::check::check_core::{ build_check_relation_targets, load_results_by_loader_name, record_python_run_state_results, relevant_check_functions, }; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, within}; +use crate::bindings::_helpers::compile_driver::python_calls::{call_with, within}; use crate::bindings::_helpers::cursor::overrides::{cursor_integer, cursor_timestamp}; use crate::bindings::_helpers::freshness::publication::append_freshness_records; use crate::bindings::_helpers::graph::python_node_graph::python_node_graph; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const CURSOR_OVERRIDES_MODULE: &str = "sqlbuild._native"; -const PLANNER_MODELS: &str = "sqlbuild._native"; -const BUILD_MODELS: &str = "sqlbuild.executor.build.models"; -const PIPELINE_RUN_MODULE: &str = "sqlbuild.executor.pipeline.main.run"; -const COST_CONTEXT_MODULE: &str = "sqlbuild._native"; -const CHECKS_MODULE: &str = "sqlbuild.executor.python_nodes.main._checks"; -const EXECUTOR_PYTHON_MODELS: &str = "sqlbuild.executor.python_nodes.models"; const EXECUTION_COMMAND: &str = "sqb build"; const SUCCESS_STATUS: &str = "success"; const NODE_START: [&str; 2] = ["name", "resource_kind"]; @@ -104,7 +100,7 @@ fn run_context_facts( + count(&discovered, "task_functions")? + count(&discovered, "asset_functions")?; let resolved: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "describe_connection_warehouse")?, + ®istry::native(py, NativeAttr::DescribeConnectionWarehouse)?, &[ ("discovered_inputs", discovered), ("selected_target", project.getattr("effective_target_name")?), @@ -178,7 +174,7 @@ fn cursors<'py>( let mut overrides: Bound<'py, PyAny> = plan.getattr("cursor_overrides")?; if !overrides.is_truthy()? { overrides = call_with( - &imported(py, CURSOR_OVERRIDES_MODULE, "type_cursor_overrides")?, + ®istry::native(py, NativeAttr::TypeCursorOverrides)?, &[( "cursor_overrides", context.request.getattr("cursor_overrides")?, @@ -186,7 +182,7 @@ fn cursors<'py>( )?; } if !overrides.is_truthy()? { - overrides = imported(py, PLANNER_MODELS, "CursorOverrides")?.call0()?; + overrides = registry::class(py, RecordId::CursorOverrides)?.call0()?; } Ok([ cursor_timestamp(&overrides.getattr("start_ts")?)?, @@ -206,7 +202,7 @@ pub(crate) fn prepare_build_execution<'py>( let plan: Bound<'py, PyAny> = pipeline.getattr("plan_output")?; let debug: bool = context.request_flag("debug")? || context.request_flag("json_output")?; let callbacks: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "BuildProgressCallbacks")?, + ®istry::native(py, NativeAttr::BuildProgressCallbacks)?, &[ ("plan", plan.clone()), ("use_color", boolean(py, context.use_color)), @@ -223,7 +219,7 @@ pub(crate) fn prepare_build_execution<'py>( write_run_context(context, pipeline)?; } else { call_with( - &imported(py, NATIVE_MODULE, "write_execution_header")?, + ®istry::native(py, NativeAttr::WriteExecutionHeader)?, &[ ("stream", context.stream.clone()), ("command", text(py, EXECUTION_COMMAND)), @@ -234,7 +230,7 @@ pub(crate) fn prepare_build_execution<'py>( )?; } let connection_progress: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "ConnectionProgressReporter")?, + ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, &[ ("adapter_name", context.get("adapter_name")?), ("stream", context.stream.clone()), @@ -292,7 +288,7 @@ pub(crate) fn execute_build_plan<'py>( let [start_ts, end_ts, start_int, end_int] = &preparation.cursors; let request = |name: &str| context.request.getattr(name); let runtime: Bound<'py, PyAny> = call_with( - &imported(py, BUILD_MODELS, "BuildRuntimeParams")?, + &host::entry(py, ExecutorHost::BuildRuntimeParams)?, &[ ("run_id", project.getattr("run_id")?), ( @@ -324,7 +320,7 @@ pub(crate) fn execute_build_plan<'py>( ( "project_relations", call_with( - &imported(py, NATIVE_MODULE, "build_project_relation_targets")?, + ®istry::native(py, NativeAttr::BuildProjectRelationTargets)?, &[("adapter", adapter.clone()), ("plan_output", plan.clone())], )?, ), @@ -332,7 +328,7 @@ pub(crate) fn execute_build_plan<'py>( )?; let connection: &Bound<'py, PyAny> = &preparation.connection_progress; let build_callbacks: Bound<'py, PyAny> = call_with( - &imported(py, BUILD_MODELS, "BuildCallbacks")?, + &host::entry(py, ExecutorHost::BuildCallbacks)?, &[ ("on_migration_progress", line_writer(&context.stream)?), ( @@ -373,7 +369,7 @@ pub(crate) fn execute_build_plan<'py>( ], )?; let customizations: Bound<'py, PyAny> = call_with( - &imported(py, BUILD_MODELS, "BuildCustomizations")?, + &host::entry(py, ExecutorHost::BuildCustomizations)?, &[ ( "custom_materializations", @@ -383,7 +379,7 @@ pub(crate) fn execute_build_plan<'py>( ], )?; let initial_state: Bound<'py, PyAny> = call_with( - &imported(py, BUILD_MODELS, "BuildInitialState")?, + &host::entry(py, ExecutorHost::BuildInitialState)?, &[ ("precompleted_keys", lifecycle.precompleted_keys(py)?), ("initial_load_results", lifecycle.ingress_load_results(py)), @@ -391,7 +387,7 @@ pub(crate) fn execute_build_plan<'py>( ], )?; let result: Bound<'py, PyAny> = call_with( - &imported(py, PIPELINE_RUN_MODULE, "run_build_pipeline")?, + &host::entry(py, ExecutorHost::RunBuildPipeline)?, &[ ("plan", plan.clone()), ("connection_config", connection_config.clone()), @@ -430,7 +426,7 @@ fn check_runtime<'py>( .map(|check| check.getattr("name")) .collect::>()?; call_with( - &imported(py, EXECUTOR_PYTHON_MODELS, "PythonNodeRuntime")?, + &host::entry(py, ExecutorHost::PythonNodeRuntime)?, &[ ("adapter", adapter.clone()), ("connection_config", context.get("connection_config")?), @@ -451,7 +447,7 @@ fn check_runtime<'py>( ( "project_relations", call_with( - &imported(py, NATIVE_MODULE, "build_project_relation_targets")?, + ®istry::native(py, NativeAttr::BuildProjectRelationTargets)?, &[ ("adapter", adapter), ("plan_output", pipeline.getattr("plan_output")?), @@ -511,7 +507,7 @@ pub(crate) fn run_post_build_python_checks<'py>( adapter.call_method1("connect", (context.get("connection_config")?,))?; let executed: PyResult> = (|| { let run_state: Bound<'py, PyAny> = - imported(py, EXECUTOR_PYTHON_MODELS, "PythonNodeRunState")?.call0()?; + host::entry(py, ExecutorHost::PythonNodeRunState)?.call0()?; record_python_run_state_results( &discovered, &run_state, @@ -519,14 +515,14 @@ pub(crate) fn run_post_build_python_checks<'py>( Some(&plan.getattr("source_map")?), )?; let callbacks: Bound<'py, PyAny> = call_with( - &imported(py, EXECUTOR_PYTHON_MODELS, "PythonCheckCallbacks")?, + &host::entry(py, ExecutorHost::PythonCheckCallbacks)?, &[( "on_check_complete", preparation.callbacks.getattr("write_execution_event")?, )], )?; call_with( - &imported(py, CHECKS_MODULE, "execute_python_checks")?, + &host::entry(py, ExecutorHost::ExecutePythonChecks)?, &[ ("check_functions", tuple(py, &checks)?), ("python_graph", graph.clone()), @@ -546,7 +542,7 @@ pub(crate) fn run_post_build_python_checks<'py>( let check_results: Bound<'py, PyAny> = executed?; closed?; call_with( - &imported(py, NATIVE_MODULE, "write_check_results")?, + ®istry::native(py, NativeAttr::WriteCheckResults)?, &[ ("stream", context.stream.clone()), ("results", check_results.clone()), @@ -573,7 +569,7 @@ pub(crate) fn execute_direct_build<'py>( } else { context.get("adapter_name")? }; - let cost_context: Bound<'py, PyAny> = imported(py, COST_CONTEXT_MODULE, "CostContext")?; + let cost_context: Bound<'py, PyAny> = registry::native(py, NativeAttr::CostContext)?; let options: Bound<'py, PyDict> = PyDict::new(py); options.set_item("run_id", &run_id)?; options.set_item("resource_type", "run")?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/build_command/invocation.rs b/crates/sqlbuild-python/src/bindings/_helpers/build_command/invocation.rs index b7dd57683a..06f649fff6 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/invocation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/invocation.rs @@ -12,13 +12,9 @@ use crate::bindings::_helpers::build_command::support::{boolean, or_none, text}; use crate::bindings::_helpers::command_runtime::adapters::resolve_cli_adapter; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const SETTINGS_MODULE: &str = "sqlbuild._native"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; -const TARGET_CONFIG_MODULE: &str = "sqlbuild._native"; -const TARGET_NAME_MODULE: &str = "sqlbuild._native"; -const CONTRACT_MODELS: &str = "sqlbuild._native"; const DEFER_CONFLICT_CODE: &str = "C408"; /// One build's request, resolved invocation and output settings. @@ -59,7 +55,7 @@ fn target_config<'py>( ) -> PyResult> { let py: Python<'py> = discovered.py(); call_with( - &imported(py, TARGET_CONFIG_MODULE, "resolve_target_config")?, + ®istry::native(py, NativeAttr::ResolveTargetConfig)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), @@ -75,7 +71,7 @@ fn execution_limits<'py>( ) -> PyResult> { let py: Python<'py> = discovered.py(); let limits: Bound<'py, PyAny> = if target_name.is_none() { - imported(py, CONTRACT_MODELS, "ExecutionLimitsConfig")?.call0()? + registry::class(py, RecordId::ExecutionLimitsConfig)?.call0()? } else { target_config(discovered, target_name)?.getattr("execution_limits")? }; @@ -106,7 +102,7 @@ pub(crate) fn resolve_build_context<'py>( imported(py, "pathlib", "Path")?.call_method0("cwd") })?; let discovered: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[ ("project_dir", project_dir.clone()), ("extract_output_column_locations", boolean(py, false)), @@ -114,7 +110,7 @@ pub(crate) fn resolve_build_context<'py>( )?; let selected_target: Bound<'py, PyAny> = request.getattr("selected_target")?; let target_name: Bound<'py, PyAny> = call_with( - &imported(py, TARGET_NAME_MODULE, "resolve_target_name")?, + ®istry::native(py, NativeAttr::ResolveTargetName)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), @@ -139,7 +135,7 @@ pub(crate) fn resolve_build_context<'py>( )); } let adapter_name: String = call_with( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), @@ -150,7 +146,7 @@ pub(crate) fn resolve_build_context<'py>( let limits: Bound<'py, PyAny> = execution_limits(&discovered, &target_name, (&adapter_name, &adapter))?; let connection_config: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "resolve_project_connection_config")?, + ®istry::native(py, NativeAttr::ResolveProjectConnectionConfig)?, &[ ("discovered_inputs", discovered.clone()), ("project_dir", project_dir.clone()), @@ -168,7 +164,7 @@ pub(crate) fn resolve_build_context<'py>( "stdout" })?; let connection_progress: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "ConnectionProgressReporter")?, + ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, &[ ("adapter_name", text(py, &adapter_name)), ("stream", stream.clone()), @@ -176,7 +172,7 @@ pub(crate) fn resolve_build_context<'py>( ], )?; let planning_progress: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "PlanningProgressReporter")?, + ®istry::native(py, NativeAttr::PlanningProgressReporter)?, &[ ("stream", stream.clone()), ("use_color", boolean(py, use_color)), @@ -186,7 +182,7 @@ pub(crate) fn resolve_build_context<'py>( let should_load_sources: bool = request.getattr("reload_sources")?.is_truthy()? || if load_sources.is_none() { call_with( - &imported(py, SETTINGS_MODULE, "build_effective_settings_config")?, + ®istry::native(py, NativeAttr::BuildEffectiveSettingsConfig)?, &[("discovered_inputs", discovered.clone())], )? .getattr("auto_load_sources")? @@ -195,7 +191,7 @@ pub(crate) fn resolve_build_context<'py>( load_sources.is_truthy()? }; let invocation: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "BuildInvocation")?, + ®istry::class(py, RecordId::BuildInvocation)?, &[ ("effective_project_dir", project_dir), ("discovered_inputs", discovered), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/build_command/lifecycle.rs b/crates/sqlbuild-python/src/bindings/_helpers/build_command/lifecycle.rs index 968c5fc8c1..6cece1234f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/lifecycle.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/lifecycle.rs @@ -16,16 +16,12 @@ use crate::bindings::_helpers::build_command::support::{ }; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; use crate::bindings::_helpers::graph::python_node_graph::python_node_graph; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const LOADER_DAG_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS: &str = "sqlbuild._native"; const COMPILE_TYPES: &str = "sqlbuild.compiler.compile.types"; -const INGRESS_MODULE: &str = "sqlbuild.executor.python_nodes.main._ingress"; -const READ_SIDE_MODULE: &str = "sqlbuild.executor.python_nodes.main.read_side"; -const EXECUTOR_PYTHON_MODELS: &str = "sqlbuild.executor.python_nodes.models"; const SUCCESS_STATUS: &str = "success"; /// The cursor window and output settings the Python lifecycle runs with. @@ -265,7 +261,7 @@ fn node_runtime<'py>( let py: Python<'py> = connection.py(); let [start_ts, end_ts, start_int, end_int] = &parts.inputs.cursors; call_with( - &imported(py, EXECUTOR_PYTHON_MODELS, "PythonNodeRuntime")?, + &host::entry(py, ExecutorHost::PythonNodeRuntime)?, &[ ("adapter", parts.adapter.clone()), ("connection_config", parts.connection_config.clone()), @@ -302,7 +298,7 @@ fn lifecycle_plan<'py>( let plan_output: Bound<'py, PyAny> = pipeline.getattr("plan_output")?; let graph: Bound<'py, PyAny> = python_node_graph(discovered)?; let selection: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "PythonSqlRunSelection")?, + ®istry::class(py, RecordId::PythonSqlRunSelection)?, &[ ( "sql_keys", @@ -313,12 +309,12 @@ fn lifecycle_plan<'py>( ], )?; let plan: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "build_python_sql_run_lifecycle")?, + ®istry::native(py, NativeAttr::BuildPythonSqlRunLifecycle)?, &[("selection", selection), ("python_graph", graph.clone())], )?; let source_type: Bound<'py, PyAny> = imported(py, COMPILE_TYPES, "CompiledResourceType")?.getattr("SOURCE")?; - let key_class: Bound<'py, PyAny> = imported(py, COMPILE_MODELS, "CompiledObjectKey")?; + let key_class: Bound<'py, PyAny> = registry::class(py, RecordId::CompiledObjectKey)?; let mut keys: Vec> = Vec::new(); for name in plan.getattr("ingress_loader_names")?.try_iter()? { keys.push(call_with( @@ -331,7 +327,7 @@ fn lifecycle_plan<'py>( source_map.call_method1( "update", (call_with( - &imported(py, LOADER_DAG_MODULE, "build_intermediate_source_map")?, + ®istry::native(py, NativeAttr::BuildIntermediateSourceMap)?, &[ ("project", pipeline.getattr("project")?), ("selected_keys", frozenset(py, &keys)?), @@ -395,7 +391,7 @@ pub(crate) fn prepare_direct_python_lifecycle<'py>( )], )?; let relation_targets: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "build_python_relation_targets")?, + ®istry::native(py, NativeAttr::BuildPythonRelationTargets)?, &[ ("adapter", adapter.clone()), ("project", project.clone()), @@ -404,7 +400,7 @@ pub(crate) fn prepare_direct_python_lifecycle<'py>( ], )?; let project_relations: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "build_project_relation_targets")?, + ®istry::native(py, NativeAttr::BuildProjectRelationTargets)?, &[ ("adapter", adapter.clone()), ("plan_output", plan_output.clone()), @@ -433,7 +429,7 @@ pub(crate) fn prepare_direct_python_lifecycle<'py>( adapter.call_method1("connect", (connection_config.clone(),))?; let executed: PyResult> = (|| { let callbacks: Bound<'py, PyAny> = call_with( - &imported(py, EXECUTOR_PYTHON_MODELS, "IngressCallbacks")?, + &host::entry(py, ExecutorHost::IngressCallbacks)?, &[ ("use_color", boolean(py, context.use_color)), ("on_node_start", inputs.on_node_start.clone()), @@ -441,7 +437,7 @@ pub(crate) fn prepare_direct_python_lifecycle<'py>( ], )?; call_with( - &imported(py, INGRESS_MODULE, "run_ingress_python_loader_nodes")?, + &host::entry(py, ExecutorHost::RunIngressPythonLoaderNodes)?, &[ ("python_graph", lifecycle.graph.clone()), ("selected_python_names", ingress_names.clone()), @@ -480,11 +476,7 @@ pub(crate) fn prepare_direct_python_lifecycle<'py>( } let scheduled: Vec = scheduled.into_iter().collect(); let tracker: Bound<'py, PyAny> = call_with( - &imported( - py, - READ_SIDE_MODULE, - "create_read_side_python_execution_tracker", - )?, + &host::entry(py, ExecutorHost::CreateReadSidePythonExecutionTracker)?, &[ ("python_graph", lifecycle.graph.clone()), ("selected_python_names", names_set(py, &read_side)?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/build_command/planning.rs b/crates/sqlbuild-python/src/bindings/_helpers/build_command/planning.rs index 6ba0587a4c..e81babcaa9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/planning.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/planning.rs @@ -14,13 +14,12 @@ use crate::bindings::_helpers::build_command::support::{ boolean, emit, joined, keyword_relay, none, }; use crate::bindings::_helpers::cli::progress_text::with_verbose_inspection; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const EXTERNAL_REFS_MODULE: &str = "sqlbuild._native"; -const RUNTIME_CONTRACT_MODELS: &str = "sqlbuild.runtime.contracts.models"; const CONNECTION_TIMING: [&str; 2] = ["connection_count", "elapsed_seconds"]; /// Clone out-of-selection boundaries from the defer-clone origin before planning. @@ -40,18 +39,14 @@ fn request_options<'py>(context: &BuildContext<'py>) -> PyResult = call_with( - &imported( - py, - EXTERNAL_REFS_MODULE, - "resolve_external_sql_reference_resolver", - )?, + ®istry::native(py, NativeAttr::ResolveExternalSqlReferenceResolver)?, &[ ("project_dir", context.get("effective_project_dir")?), ("discovered_inputs", context.get("discovered_inputs")?), ], )?; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "CompilePipelineOptions")?, + ®istry::class(py, RecordId::CompilePipelineOptions)?, &[ ("no_sql_validation", field("no_sql_validation")?), ("no_cache", field("no_cache")?), @@ -85,7 +80,7 @@ pub(crate) fn compile_build_plan<'py>(context: &BuildContext<'py>) -> PyResult = context.get("connection_progress")?; let hooks: Bound<'py, PyAny> = call_with( - &imported(py, RUNTIME_CONTRACT_MODELS, "ConnectionHooks")?, + &host::entry(py, ExecutorHost::ConnectionHooks)?, &[ ( "on_progress", @@ -114,7 +109,7 @@ pub(crate) fn compile_build_plan<'py>(context: &BuildContext<'py>) -> PyResult = pipeline.getattr("plan_output")?; let manifest: Bound<'_, PyAny> = if context.request_flag("manifest")? { call_with( - &imported(py, NATIVE_MODULE, "build_manifest")?, + ®istry::native(py, NativeAttr::BuildManifest)?, &[ ("project", pipeline.getattr("project")?), ("plan_output", plan.clone()), @@ -216,7 +211,7 @@ pub(crate) fn write_compile_target( none(py) }; call_with( - &imported(py, NATIVE_MODULE, "write_compile_target")?, + ®istry::native(py, NativeAttr::WriteCompileTarget)?, &[ ( "target_dir", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/build_command/policies.rs b/crates/sqlbuild-python/src/bindings/_helpers/build_command/policies.rs index 3b0714a9e5..d00dc913d3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/policies.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/policies.rs @@ -16,9 +16,10 @@ use sqlbuild_core::build_command::models::{ TableTypeFact, }; -use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; -const ERRORS_MODULE: &str = "sqlbuild._native"; const SNAPSHOT_VALUE: &str = "snapshot"; const FULL_REFRESH_REASON: &str = "full_refresh"; const SKIP_ACTION: &str = "skip"; @@ -45,7 +46,7 @@ pub(crate) fn build_command_error(py: Python<'_>, error: BuildCommandError) -> P let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("code", error.code)?; options.set_item("help", error.help)?; - imported(py, ERRORS_MODULE, "CliUserError")?.call((error.message,), Some(&options)) + registry::error(py, ErrorId::CliUser)?.call((error.message,), Some(&options)) })(); match raised { Ok(error) => PyErr::from_value(error), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/build_progress/build_callbacks.rs b/crates/sqlbuild-python/src/bindings/_helpers/build_progress/build_callbacks.rs index 472ad71d1d..84c81681e2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_progress/build_callbacks.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_progress/build_callbacks.rs @@ -30,8 +30,9 @@ use crate::bindings::_helpers::build_progress::terminal::{ terminal_columns, tty_flag, write, }; use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported, within}; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; const RUNTIME_LOGGER: &str = "sqlbuild.executor.runtime"; const PASS_OUTCOME: &str = "pass"; const SCHEDULER_DIAGNOSTIC_LIMIT: usize = 50; @@ -407,7 +408,8 @@ impl BuildProgressCallbacks { let writer_options: Bound<'_, PyDict> = PyDict::new(py); writer_options.set_item("path", path)?; let event_writer: Bound<'_, PyAny> = - imported(py, NATIVE_MODULE, "ExecutionEventWriter")?.call((), Some(&writer_options))?; + registry::native(py, NativeAttr::ExecutionEventWriter)? + .call((), Some(&writer_options))?; let facts: BuildPlanFacts = build_plan_facts(&plan)?; let projector: Option> = projector(py)?; if let Some(projector) = &projector { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/build_progress/terminal.rs b/crates/sqlbuild-python/src/bindings/_helpers/build_progress/terminal.rs index ebf0e651f7..83cf0620ae 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_progress/terminal.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_progress/terminal.rs @@ -6,6 +6,8 @@ use pyo3::{IntoPyObjectExt, pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::build_progress::transient_lines::shared_coordinator; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; const DEFAULT_TERMINAL_SIZE: (i64, i64) = (120, 24); const SPINNER_JOIN_SECONDS: f64 = 0.2; @@ -48,7 +50,7 @@ pub(crate) fn tty_flag(stream: &Bound<'_, PyAny>) -> PyResult { /// The current native progress projector, if a projection is active. pub(crate) fn projector(py: Python<'_>) -> PyResult>> { let projector: Bound<'_, PyAny> = - imported(py, "sqlbuild._native", "current_native_progress_projector")?.call0()?; + registry::native(py, NativeAttr::CurrentNativeProgressProjector)?.call0()?; Ok((!projector.is_none()).then(|| projector.unbind())) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cache/native_store.rs b/crates/sqlbuild-python/src/bindings/_helpers/cache/native_store.rs index 63cd1b55b9..b11fdbce05 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cache/native_store.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cache/native_store.rs @@ -17,11 +17,12 @@ use sqlbuild_cache::store::models::NativeStore; use std::collections::HashSet; use std::path::{Path, PathBuf}; +use crate::bindings::_helpers::native_host::facade::FacadeHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use crate::bindings::types::CompilerDetach; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const PACKAGE_MODULE: &str = "sqlbuild"; - /// Hexadecimal digest of length-prefixed UTF-8 `parts`, the digest every native cache key uses. #[pyfunction] pub(crate) fn content_digest(parts: Vec) -> String { @@ -92,17 +93,13 @@ pub(crate) fn save_store( pub(crate) fn compiled_code_identity(py: Python<'_>) -> PyResult { static INSTALLED: PyOnceLock = PyOnceLock::new(); let installed: &String = INSTALLED.get_or_try_init(py, || installed_identity(py))?; - let build_identity: String = py - .import(NATIVE_MODULE)? - .getattr("BUILD_IDENTITY")? - .extract()?; + let build_identity: String = registry::native(py, NativeAttr::BuildIdentity)?.extract()?; Ok(compiled(installed, &build_identity)) } /// `compiled_code_identity`, looked up on the extension module so tests can replace it. pub(crate) fn current_code_identity(py: Python<'_>) -> PyResult { - py.import(NATIVE_MODULE)? - .getattr("compiled_code_identity")? + registry::native(py, NativeAttr::CompiledCodeIdentity)? .call0()? .extract() } @@ -129,7 +126,7 @@ fn installed_identity(py: Python<'_>) -> PyResult { .getattr("minor")? .extract()?, ); - let package_file: PathBuf = py.import(PACKAGE_MODULE)?.getattr("__file__")?.extract()?; + let package_file: PathBuf = host::entry(py, FacadeHost::PackageFile)?.extract()?; let (mut facts, editable) = py .compiler_detach(|| Ok(installed_code_facts(&search_paths))) .map_err(PyRuntimeError::new_err)?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/check/check_command.rs b/crates/sqlbuild-python/src/bindings/_helpers/check/check_command.rs index 7e3ff2fac1..f23c9beec0 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/check/check_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/check/check_command.rs @@ -24,23 +24,13 @@ use crate::bindings::_helpers::compile_driver::python_calls::{call_with, importe use crate::bindings::_helpers::compile_outputs::runtime_target::write_python_check_runtime_target; use crate::bindings::_helpers::graph::python_node_graph::python_node_graph; use crate::bindings::_helpers::graph::python_selection::python_node_names; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ERRORS_MODULE: &str = "sqlbuild._native"; -const EXTERNAL_REFS_MODULE: &str = "sqlbuild._native"; -const REJECT_UNIT_TESTS_MODULE: &str = "sqlbuild._native"; -const STATIC_COMMAND_MODULE: &str = "sqlbuild._native"; -const RELATION_PLAN_MODULE: &str = "sqlbuild._native"; -const SELECTOR_PARSE_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS: &str = "sqlbuild._native"; const COMPILE_TYPES: &str = "sqlbuild.compiler.compile.types"; -const PYTHON_NODE_TYPES: &str = "sqlbuild.compiler.python_nodes.types"; -const EXECUTOR_MODELS: &str = "sqlbuild.executor.python_nodes.models"; -const CHECKS_MODULE: &str = "sqlbuild.executor.python_nodes.main._checks"; -const INGRESS_MODULE: &str = "sqlbuild.executor.python_nodes.main._ingress"; -const PROVIDER_SESSION_MODULE: &str = "sqlbuild.provider.main.session"; const GRAPH_OPERATOR_CODE: &str = "C681"; const GRAPH_OPERATOR_MESSAGE: &str = "sqb check selectors do not support graph operators; \ select checks directly without '+', '~', or path-between expansion"; @@ -62,7 +52,7 @@ fn user_error(py: Python<'_>, (code, message): (&str, &str)) -> PyErr { let raised = (|| { let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("code", code)?; - imported(py, ERRORS_MODULE, "CliUserError")?.call((message,), Some(&options)) + registry::error(py, ErrorId::CliUser)?.call((message,), Some(&options)) })(); match raised { Ok(error) => PyErr::from_value(error), @@ -80,8 +70,8 @@ fn empty_tuple(py: Python<'_>) -> Bound<'_, PyAny> { /// Refuse `+`, `~` and path selectors, splitting selectors as Python's `split` does. fn refuse_graph_selectors(py: Python<'_>, selectors: &[Bound<'_, PyAny>]) -> PyResult<()> { - let parse: Bound<'_, PyAny> = imported(py, SELECTOR_PARSE_MODULE, "parse_project_selector")?; - let path_selector: Bound<'_, PyAny> = imported(py, NATIVE_RECORDS_MODULE, "PathSelector")?; + let parse: Bound<'_, PyAny> = registry::native(py, NativeAttr::ParseProjectSelector)?; + let path_selector: Bound<'_, PyAny> = registry::class(py, RecordId::PathSelector)?; for selector in selectors { for token in selector.call_method0("split")?.try_iter()? { for part in token?.call_method1("split", (",",))?.try_iter()? { @@ -132,7 +122,7 @@ fn selected_check_names<'py>( .collect::>()?; refuse_graph_selectors(py, &selectors)?; let check_kind: Bound<'py, PyAny> = - imported(py, PYTHON_NODE_TYPES, "PythonNodeKind")?.getattr("CHECK")?; + host::entry(py, UserCodeHost::PythonNodeKind)?.getattr("CHECK")?; if select.is_truthy()? { let selected: Bound<'py, PyAny> = python_nodes(graph, (select, exclude))?; let mut non_checks: Vec = Vec::new(); @@ -174,7 +164,7 @@ fn compile_pipeline<'py>( &[("selected_names", check_names.clone().into_any())], )?; let closure: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "transitive_closure_many")?, + ®istry::native(py, NativeAttr::TransitiveClosureMany)?, &[ ("starts", check_names.clone().into_any()), ("edges", graph.getattr("upstream_deps")?), @@ -185,7 +175,7 @@ fn compile_pipeline<'py>( ], )?; let loader_kind: Bound<'py, PyAny> = - imported(py, PYTHON_NODE_TYPES, "PythonNodeKind")?.getattr("LOADER")?; + host::entry(py, UserCodeHost::PythonNodeKind)?.getattr("LOADER")?; let mut loaders: Vec> = Vec::new(); for name in closure.try_iter()? { let name: Bound<'py, PyAny> = name?; @@ -194,10 +184,10 @@ fn compile_pipeline<'py>( } } let loaders: Bound<'py, PyFrozenSet> = PyFrozenSet::new(py, loaders)?; - let key_class: Bound<'py, PyAny> = imported(py, COMPILE_MODELS, "CompiledObjectKey")?; + let key_class: Bound<'py, PyAny> = registry::class(py, RecordId::CompiledObjectKey)?; let source_type: Bound<'py, PyAny> = imported(py, COMPILE_TYPES, "CompiledResourceType")?.getattr("SOURCE")?; - let ref_key: Bound<'py, PyAny> = imported(py, NATIVE_MODULE, "sql_ref_key")?; + let ref_key: Bound<'py, PyAny> = registry::native(py, NativeAttr::SqlRefKey)?; let mut keys: Vec> = Vec::new(); for reference in required_refs.try_iter()? { keys.push(ref_key.call1((reference?,))?); @@ -218,18 +208,14 @@ fn compile_pipeline<'py>( } let keys: Bound<'py, PyAny> = PyFrozenSet::new(py, keys)?.into_any(); let resolver: Bound<'py, PyAny> = call_with( - &imported( - py, - EXTERNAL_REFS_MODULE, - "resolve_external_sql_reference_resolver", - )?, + ®istry::native(py, NativeAttr::ResolveExternalSqlReferenceResolver)?, &[ ("project_dir", invocation.getattr("effective_project_dir")?), ("discovered_inputs", discovered.clone()), ], )?; let options: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "CompilePipelineOptions")?, + ®istry::class(py, RecordId::CompilePipelineOptions)?, &[ ("selected_target", request.getattr("selected_target")?), ("no_sql_validation", request.getattr("no_sql_validation")?), @@ -242,7 +228,7 @@ fn compile_pipeline<'py>( ], )?; let context: Bound<'py, PyAny> = call_with( - &imported(py, STATIC_COMMAND_MODULE, "compile_static_command_context")?, + ®istry::native(py, NativeAttr::CompileStaticCommandContext)?, &[ ("discovered_inputs", discovered), ("adapter", invocation.getattr("adapter")?), @@ -258,7 +244,7 @@ fn compile_pipeline<'py>( ], )?; let plan_output: Bound<'py, PyAny> = call_with( - &imported(py, RELATION_PLAN_MODULE, "build_relation_command_plan")?, + ®istry::native(py, NativeAttr::BuildRelationCommandPlan)?, &[ ("project", context.getattr("project")?), ("scope", context.getattr("scope")?), @@ -266,7 +252,7 @@ fn compile_pipeline<'py>( ], )?; call_with( - &imported(py, NATIVE_MODULE, "build_static_pipeline_result")?, + ®istry::native(py, NativeAttr::BuildStaticPipelineResult)?, &[("context", context), ("plan_output", plan_output)], ) } @@ -292,7 +278,7 @@ fn python_selection<'py>( let py: Python<'py> = request.py(); let python_graph: Bound<'py, PyAny> = python_node_graph(discovered)?; call_with( - &imported(py, REJECT_UNIT_TESTS_MODULE, "reject_unit_test_selectors")?, + ®istry::native(py, NativeAttr::RejectUnitTestSelectors)?, &[ ("select", request.getattr("select")?), ("exclude", request.getattr("exclude")?), @@ -322,14 +308,14 @@ fn prepare<'py>( } } let selection: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "PythonSqlRunSelection")?, + ®istry::class(py, RecordId::PythonSqlRunSelection)?, &[ ("sql_keys", PyFrozenSet::empty(py)?.into_any()), ("python_node_names", PyFrozenSet::empty(py)?.into_any()), ], )?; let lifecycle_plan: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "build_python_sql_run_lifecycle")?, + ®istry::native(py, NativeAttr::BuildPythonSqlRunLifecycle)?, &[ ("selection", selection), ("python_graph", python_graph.clone()), @@ -342,7 +328,7 @@ fn prepare<'py>( let project: Bound<'py, PyAny> = pipeline.getattr("project")?; let plan_output: Bound<'py, PyAny> = pipeline.getattr("plan_output")?; let relation_targets: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "build_python_relation_targets")?, + ®istry::native(py, NativeAttr::BuildPythonRelationTargets)?, &[ ("adapter", adapter.clone()), ("project", project.clone()), @@ -368,7 +354,7 @@ fn prepare<'py>( )?, ), project_relations: call_with( - &imported(py, NATIVE_MODULE, "build_project_relation_targets")?, + ®istry::native(py, NativeAttr::BuildProjectRelationTargets)?, &[("adapter", adapter), ("plan_output", plan_output)], )?, python_graph, @@ -403,10 +389,7 @@ fn node_runtime<'py>( fields.push(("project_relations", prepared.project_relations.clone())); } fields.push(("providers", providers.clone())); - call_with( - &imported(py, EXECUTOR_MODELS, "PythonNodeRuntime")?, - &fields, - ) + call_with(&host::entry(py, ExecutorHost::PythonNodeRuntime)?, &fields) } fn record_run_state<'py>( @@ -440,11 +423,11 @@ fn run_checks<'py>( let plan_output: Bound<'py, PyAny> = prepared.pipeline.getattr("plan_output")?; let source_map: Bound<'py, PyAny> = plan_output.getattr("source_map")?; let callbacks: Bound<'py, PyAny> = call_with( - &imported(py, EXECUTOR_MODELS, "IngressCallbacks")?, + &host::entry(py, ExecutorHost::IngressCallbacks)?, &[("use_color", invocation.getattr("use_color")?)], )?; let ingress: Bound<'py, PyAny> = call_with( - &imported(py, INGRESS_MODULE, "execute_ingress_python_loader_nodes")?, + &host::entry(py, ExecutorHost::ExecuteIngressPythonLoaderNodes)?, &[ ("python_graph", prepared.python_graph.clone()), ( @@ -511,11 +494,11 @@ fn run_checks<'py>( )? .into_any(); let check_callbacks: Bound<'py, PyAny> = call_with( - &imported(py, EXECUTOR_MODELS, "PythonCheckCallbacks")?, + &host::entry(py, ExecutorHost::PythonCheckCallbacks)?, &[("on_check_complete", on_complete)], )?; call_with( - &imported(py, CHECKS_MODULE, "execute_python_checks")?, + &host::entry(py, ExecutorHost::ExecutePythonChecks)?, &[ ("check_functions", prepared.check_functions.clone()), ("python_graph", prepared.python_graph.clone()), @@ -571,10 +554,10 @@ fn execute<'py>( let connection: Bound<'py, PyAny> = adapter.call_method1("connect", (invocation.getattr("connection_config")?,))?; let event_writer: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "ExecutionEventWriter")?.call0()?; + registry::native(py, NativeAttr::ExecutionEventWriter)?.call0()?; let executed: PyResult>> = (|| { let projector: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "current_native_progress_projector")?.call0()?; + registry::native(py, NativeAttr::CurrentNativeProgressProjector)?.call0()?; if !projector.is_none() { let ordinals: Bound<'py, PyDict> = PyDict::new(py); let mut total: usize = 0; @@ -635,7 +618,7 @@ fn complete<'py>( &tuple, )?; call_with( - &imported(py, NATIVE_MODULE, "write_execution_json_output")?, + ®istry::native(py, NativeAttr::WriteExecutionJsonOutput)?, &[ ( "payload", @@ -654,7 +637,7 @@ fn complete<'py>( fn run_check_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { let invocation: Bound<'_, PyAny> = resolve_reported_command_invocation( request, - &imported(py, NATIVE_MODULE, "CheckInvocation")?, + ®istry::class(py, RecordId::CheckInvocation)?, true, )?; let stream: Bound<'_, PyAny> = invocation.getattr("progress_stream")?; @@ -665,10 +648,10 @@ fn run_check_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult = prepare(request, &invocation)?; let session: Bound<'_, PyAny> = call_with( - &imported(py, PROVIDER_SESSION_MODULE, "build_provider_session")?, + &host::entry(py, UserCodeHost::BuildProviderSession)?, &[( "discovered_providers", invocation diff --git a/crates/sqlbuild-python/src/bindings/_helpers/check/check_core.rs b/crates/sqlbuild-python/src/bindings/_helpers/check/check_core.rs index b9ccb8007e..44a352eb20 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/check/check_core.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/check/check_core.rs @@ -10,18 +10,14 @@ use pyo3::{pyfunction, wrap_pyfunction}; use sqlbuild_core::build_command::errors::BuildCommandError; use crate::bindings::_helpers::build_command::policies::build_command_error; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const BUILD_TYPES: &str = "sqlbuild.executor.build.types"; -const PYTHON_NODE_TYPES: &str = "sqlbuild.compiler.python_nodes.types"; -const EXECUTOR_MODELS: &str = "sqlbuild.executor.python_nodes.models"; -const READ_SIDE_MODULE: &str = "sqlbuild.executor.python_nodes.main.read_side"; -const RELATION_LOOKUP_MODULE: &str = "sqlbuild.adapter.relations.main.relation_lookup"; -const QUALIFIED_NAME_MODULE: &str = - "sqlbuild.adapter.relations.main.resolve_relation_location_qualified_name"; const MISSING_RELATION_CODE: &str = "C682"; const MODEL_KIND: &str = "model"; const SEED_KIND: &str = "seed"; @@ -56,8 +52,8 @@ fn load_result_as_python_result<'py>( result: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = name.py(); - let execution: Bound<'py, PyAny> = imported(py, BUILD_TYPES, "ExecutionStatus")?; - let node_status: Bound<'py, PyAny> = imported(py, PYTHON_NODE_TYPES, "PythonNodeStatus")?; + let execution: Bound<'py, PyAny> = host::entry(py, ExecutorHost::ExecutionStatus)?; + let node_status: Bound<'py, PyAny> = host::entry(py, UserCodeHost::PythonNodeStatus)?; let status: Bound<'py, PyAny> = result.getattr("status")?; let mapped: Bound<'py, PyAny> = if status.eq(execution.getattr("SUCCESS")?)? { node_status.getattr("SUCCESS")? @@ -67,12 +63,12 @@ fn load_result_as_python_result<'py>( node_status.getattr("FAILED")? }; call_with( - &imported(py, EXECUTOR_MODELS, "PythonNodeExecutionResult")?, + &host::entry(py, ExecutorHost::PythonNodeExecutionResult)?, &[ ("node_name", name.clone()), ( "kind", - imported(py, PYTHON_NODE_TYPES, "PythonNodeKind")?.getattr("LOADER")?, + host::entry(py, UserCodeHost::PythonNodeKind)?.getattr("LOADER")?, ), ("status", mapped), ("payload", result.clone()), @@ -175,7 +171,7 @@ pub(crate) fn build_check_relation_targets<'py>( &[("selected_names", selected_python_names)], )?; call_with( - &imported(py, NATIVE_MODULE, "build_python_relation_targets")?, + ®istry::native(py, NativeAttr::BuildPythonRelationTargets)?, &[ ("adapter", adapter.clone()), ("project", pipeline_result.getattr("project")?), @@ -194,11 +190,11 @@ pub(crate) fn relevant_check_functions<'py>( ) -> PyResult>> { let py: Python<'py> = discovered_inputs.py(); let excluded: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "resolve_python_check_excludes")?, + ®istry::native(py, NativeAttr::ResolvePythonCheckExcludes)?, &[("graph", python_graph.clone()), ("exclude", exclude)], )?; let check_kind: Bound<'py, PyAny> = - imported(py, PYTHON_NODE_TYPES, "PythonNodeKind")?.getattr("CHECK")?; + host::entry(py, UserCodeHost::PythonNodeKind)?.getattr("CHECK")?; let upstream_deps: Bound<'py, PyAny> = python_graph.getattr("upstream_deps")?; let mut check_names: Vec> = Vec::new(); for node in python_graph.getattr("nodes")?.try_iter()? { @@ -367,11 +363,7 @@ fn validate_reference_exists( ), )?; let relation: Bound<'_, PyAny> = call_with( - &imported( - py, - QUALIFIED_NAME_MODULE, - "resolve_relation_location_qualified_name", - )?, + &host::entry(py, AdapterHost::ResolveRelationLocationQualifiedName)?, &[("adapter", inputs.adapter.clone()), ("location", location)], )?; (exists, relation) @@ -438,7 +430,7 @@ fn node_runtime<'py>(inputs: &ReadSideInputs<'py>) -> PyResult let py: Python<'py> = inputs.adapter.py(); let project: Bound<'py, PyAny> = inputs.pipeline_result.getattr("project")?; call_with( - &imported(py, EXECUTOR_MODELS, "PythonNodeRuntime")?, + &host::entry(py, ExecutorHost::PythonNodeRuntime)?, &[ ("adapter", inputs.adapter.clone()), ("connection_config", inputs.connection_config.clone()), @@ -484,7 +476,7 @@ pub(crate) fn run_read_side_dependencies<'py>( } } let lookup: Bound<'py, PyAny> = call_with( - &imported(py, RELATION_LOOKUP_MODULE, "build_relation_lookup")?, + &host::entry(py, AdapterHost::BuildRelationLookup)?, &[ ("adapter", inputs.adapter.clone()), ("connection", inputs.connection.clone()), @@ -498,11 +490,7 @@ pub(crate) fn run_read_side_dependencies<'py>( return Ok(PyTuple::empty(py).into_any()); } let tracker: Bound<'py, PyAny> = call_with( - &imported( - py, - READ_SIDE_MODULE, - "create_read_side_python_execution_tracker", - )?, + &host::entry(py, ExecutorHost::CreateReadSidePythonExecutionTracker)?, &[ ("python_graph", inputs.python_graph.clone()), ("selected_python_names", read_side_names), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/check/check_results.rs b/crates/sqlbuild-python/src/bindings/_helpers/check/check_results.rs index 92955c0b94..8688e368a1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/check/check_results.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/check/check_results.rs @@ -14,10 +14,11 @@ use sqlbuild_core::check_command::models::{CheckGrouping, CheckResultText, Check use sqlbuild_core::cli_text::models::CliStyle; use sqlbuild_core::json::models::JsonValue; -use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::result_output::integration_values::json_value; -const NATIVE_MODULE: &str = "sqlbuild._native"; const WARN_SEVERITY: &str = "warn"; fn optional_text(value: Bound<'_, PyAny>) -> PyResult> { @@ -124,7 +125,7 @@ pub(crate) fn write_results( checks.insert(check.getattr("name")?.extract()?, check); } let projector: Bound<'_, PyAny> = - imported(py, NATIVE_MODULE, "current_native_progress_projector")?.call0()?; + registry::native(py, NativeAttr::CurrentNativeProgressProjector)?.call0()?; let mut groups: Vec<(String, Vec)> = Vec::new(); for result in results { let text: CheckResultText = check_result_text(result)?; @@ -169,7 +170,7 @@ pub(crate) fn results_json(results: &[Bound<'_, PyAny>]) -> PyResult { }; let py: Python<'_> = first.py(); let index: Bound<'_, PyAny> = - imported(py, NATIVE_MODULE, "current_terminal_event_index")?.call0()?; + registry::native(py, NativeAttr::CurrentTerminalEventIndex)?.call0()?; let mut texts: Vec = Vec::new(); for result in results { let name: Bound<'_, PyAny> = result.getattr("node_name")?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cli/argv.rs b/crates/sqlbuild-python/src/bindings/_helpers/cli/argv.rs index 80a6fa14cc..24d8e42913 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cli/argv.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cli/argv.rs @@ -13,9 +13,10 @@ use sqlbuild_core::text::main::python_str_repr::python_str_repr; use crate::bindings::_helpers::cli::namespace::new_cli_namespace; use crate::bindings::_helpers::cli::surrogates::{python_text, rust_text, uses_placeholders}; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const ENTRY_MODELS_MODULE: &str = "sqlbuild._native"; const INT_CONVERTER: &str = "int"; const POSITIVE_INTEGER_CONVERTER: &str = "_positive_integer"; const VARS_CONVERTER: &str = "parse_cli_vars"; @@ -255,7 +256,7 @@ pub(crate) fn parse_cli_invocation<'py>( .extract()?; let settings: ArgvSettings = ArgvSettings { columns, use_color }; let (rows, exit_code): ParsedRows = parse_cli_arguments(&argv, &defaults, settings)?; - let parsed: Bound<'py, PyAny> = imported(py, ENTRY_MODELS_MODULE, "ParsedCliInvocation")?; + let parsed: Bound<'py, PyAny> = registry::class(py, RecordId::ParsedCliInvocation)?; let Some(rows) = rows else { return call_with( &parsed, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cli/dispatch.rs b/crates/sqlbuild-python/src/bindings/_helpers/cli/dispatch.rs index 531979fcc0..f2b472f2ce 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cli/dispatch.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cli/dispatch.rs @@ -19,10 +19,9 @@ use sqlbuild_core::format_command::main::format_path_selectors::format_path_sele use sqlbuild_core::text::main::python_strip::python_strip; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, within}; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const EXCEPTIONS_MODULE: &str = "sqlbuild._native"; -const ENTRY_MODELS_MODULE: &str = "sqlbuild._native"; -const WAREHOUSES_MODULE: &str = "sqlbuild._native"; const PATHLIB_MODULE: &str = "pathlib"; const COMMENT_PREFIX: &str = "#"; const UTF8: &str = "utf-8"; @@ -120,7 +119,7 @@ impl<'py> Invocation<'py> { |error| { cli_error( py, - "CliUserError", + ErrorId::CliUser, &DiffUserError { message: error.message, code: error.code.to_owned(), @@ -148,7 +147,7 @@ impl<'py> Invocation<'py> { } let py: Python<'py> = self.py(); let (from_name, to_name) = parse_diff_name_range(&range.extract::()?) - .map_err(|error| cli_error(py, "CliUserError", &error))?; + .map_err(|error| cli_error(py, ErrorId::CliUser, &error))?; match side { DiffSide::From => from_name.into_bound_py_any(py), DiffSide::To => to_name.into_bound_py_any(py), @@ -176,7 +175,7 @@ fn path_class(py: Python<'_>) -> PyResult> { } fn class_object<'py>(py: Python<'py>, class: ClassRef) -> PyResult> { - imported(py, class.module, class.name) + registry::class(py, registry::record_named(class.name)?) } fn tuple_of<'py>(value: &Bound<'py, PyAny>) -> PyResult> { @@ -200,7 +199,7 @@ fn user_error(py: Python<'_>, failure: DispatchFailure) -> PyErr { let keywords: Bound<'_, PyDict> = PyDict::new(py); keywords.set_item("code", failure.code)?; keywords.set_item("help", failure.help)?; - imported(py, EXCEPTIONS_MODULE, "CliUserError")?.call((failure.message,), Some(&keywords)) + registry::error(py, ErrorId::CliUser)?.call((failure.message,), Some(&keywords)) })(); match raised { Ok(error) => PyErr::from_value(error), @@ -217,8 +216,7 @@ fn selector_inputs<'py>( .getattr("select")? .try_iter()? .collect::>()?; - let summary_class: Bound<'py, PyAny> = - imported(py, ENTRY_MODELS_MODULE, "SelectorFileSummary")?; + let summary_class: Bound<'py, PyAny> = registry::class(py, RecordId::SelectorFileSummary)?; let mut files: Vec> = Vec::new(); for raw_path in args.getattr("select_file")?.try_iter()? { let path: Bound<'py, PyAny> = path_class(py)?.call1((raw_path?,))?; @@ -260,7 +258,7 @@ fn call_route<'py>( let (callable, positional_count) = match handler { Some(handler) => (handler, route.shape.slot_positional), None => { - let (module, function) = route.implementation; + let function: NativeAttr = registry::attribute_named(route.implementation)?; if let Some(extra) = route.implementation_extra { keywords.set_item(extra.name, invocation.value(extra.source)?)?; } @@ -269,7 +267,7 @@ fn call_route<'py>( } else { route.shape.slot_positional }; - (imported(py, module, function)?, count) + (registry::native(py, function)?, count) } }; for (index, field) in route.arguments.iter().enumerate() { @@ -347,7 +345,7 @@ pub(crate) fn dispatch_command<'py>( .call_method0("resolve")? }; call_with( - &imported(py, WAREHOUSES_MODULE, "validate_cli_warehouse")?, + ®istry::native(py, NativeAttr::ValidateCliWarehouse)?, &[ ("cli_warehouse", warehouse.clone()), ("project_dir", project_dir), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cli/entry.rs b/crates/sqlbuild-python/src/bindings/_helpers/cli/entry.rs index b6bec490be..ba0bec4ff1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cli/entry.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cli/entry.rs @@ -17,46 +17,30 @@ use crate::bindings::_helpers::cli::dispatch::{command_facts, dispatch_command}; use crate::bindings::_helpers::cli::launch_state::offer_launch_record; use crate::bindings::_helpers::cli::observability::cli_observability_scope; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, within}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::facade::FacadeHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::output_capture::records::invocation_context; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use sqlbuild_cli::launcher::constants::LAUNCH_RECORD_OPTION; use sqlbuild_core::cli_text::main::format_coded_error::format_coded_error; use sqlbuild_core::cli_text::models::CliStyle; const NO_COLOR_OPTION: &str = "--no-color"; -const EXCEPTIONS_MODULE: &str = "sqlbuild._native"; -const COMPUTE_LOGS_MODULE: &str = - "sqlbuild.cli.commands.main.entrypoint._dispatch_with_compute_logs"; -const LIFECYCLE_EVENT_MODULE: &str = "sqlbuild.runtime.observability.main.create_lifecycle_event"; -const INVOCATION_SCOPE_MODULE: &str = "sqlbuild.runtime.observability.main.invocation_scope"; -const EXTERNAL_SCOPE_MODULE: &str = - "sqlbuild.runtime.observability.main.invocation_external_context_scope"; -const OUTPUT_CAPTURE_ERRORS_MODULE: &str = "sqlbuild.runtime.output_capture.exceptions"; -const DIAGNOSTICS_MODULE: &str = "sqlbuild.diagnostics.main.diagnostics_context"; -const TRANSIENT_LINE_MODULE: &str = "sqlbuild._native"; -const SKILLS_MODULE: &str = "sqlbuild._native"; -const RULES_ERRORS_MODULE: &str = "sqlbuild._native"; -const LINT_ERRORS_MODULE: &str = "sqlbuild._native"; -const CONTRACT_MODELS_MODULE: &str = "sqlbuild._native"; -const NATIVE_MODULE: &str = "sqlbuild._native"; const CLI_LOGGER: &str = "sqlbuild.cli"; const UNKNOWN_COMMAND: &str = "unknown"; const DIFF_PUBLICATION_CODE: &str = "C257"; const DIFF_EXECUTION_STATUS: &str = "execution_failed"; const DIFF_OUTCOME_PREFIX: &str = "Query diff outcome "; -/// Expected command failures by module and class, loaded only once a command raised. -const EXPECTED_COMMAND_FAILURES: [(&str, &str); 4] = [ - ( - "sqlbuild.executor.pipeline.exceptions", - "AuditExecutionError", - ), - ("sqlbuild._native", "DiscoveryError"), - ( - "sqlbuild.runtime.execution_limits.exceptions", - "ExecutionDurationLimitError", - ), - ("sqlbuild._native", "SpecConfigError"), +/// Expected staying-Python command failures, loaded only once a command raised. +const EXPECTED_COMMAND_FAILURES: [ExecutorHost; 2] = [ + ExecutorHost::AuditExecutionError, + ExecutorHost::ExecutionDurationLimitError, ]; +/// Expected native command failures. +const EXPECTED_NATIVE_FAILURES: [ErrorId; 2] = [ErrorId::Discovery, ErrorId::SpecConfig]; /// One parsed invocation: its namespace, injected handlers and error colour. struct Invocation<'py> { @@ -113,10 +97,6 @@ fn print_object(value: &Bound<'_, PyAny>, stream: Option<&str>, end: Option<&str Ok(()) } -fn class<'py>(py: Python<'py>, module: &str, name: &str) -> PyResult> { - imported(py, module, name) -} - /// `(code, message, help)` of an expected error as Python objects, as the CLI renders it. fn error_parts<'py>( error: &Bound<'py, PyAny>, @@ -183,13 +163,13 @@ fn write_compile_machine_error( .getattr_opt("location")? .unwrap_or_else(|| py.None().into_bound(py)); let location: Bound<'_, PyAny> = - if location.is_instance(&class(py, CONTRACT_MODELS_MODULE, "SourceLocation")?)? { + if location.is_instance(®istry::class(py, RecordId::SourceLocation)?)? { location } else { py.None().into_bound(py) }; let document: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "format_compile_error_json")?, + ®istry::native(py, NativeAttr::FormatCompileErrorJson)?, &[ ("code", code), ("message", message), @@ -205,7 +185,7 @@ fn render_diff_error<'py>( fields: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { call_with( - &imported(py, NATIVE_MODULE, "render_diff_error_json")?, + ®istry::native(py, NativeAttr::RenderDiffErrorJson)?, fields, ) } @@ -262,8 +242,8 @@ fn write_diff_machine_error<'py>( } let keywords: Bound<'py, PyDict> = PyDict::new(py); keywords.set_item("code", DIFF_PUBLICATION_CODE)?; - let publication: Bound<'py, PyAny> = - class(py, EXCEPTIONS_MODULE, "QueryDiffExecutionError")?.call( + let publication: Bound<'py, PyAny> = registry::error(py, ErrorId::QueryDiffExecution)? + .call( (format!( "failed to publish query diff output: {}", value.str()? @@ -307,19 +287,24 @@ fn exit<'py>(py: Python<'py>, code: i64) -> PyResult> { /// The kind of expected failure `error` is, or `None` when it propagates. fn failure_kind(py: Python<'_>, error: &Bound<'_, PyAny>) -> PyResult> { - if error.is_instance(&class(py, EXCEPTIONS_MODULE, "CliUserError")?)? - || error.is_instance(&class(py, RULES_ERRORS_MODULE, "RulesError")?)? + if error.is_instance(®istry::error(py, ErrorId::CliUser)?)? + || error.is_instance(®istry::error(py, ErrorId::Rules)?)? { return Ok(Some(CommandFailure::User)); } - if error.is_instance(&class(py, LINT_ERRORS_MODULE, "LintError")?)? { + if error.is_instance(®istry::error(py, ErrorId::Lint)?)? { return Ok(Some(CommandFailure::Lint)); } if error.is_instance_of::() { return Ok(Some(CommandFailure::Command)); } - for (module, name) in EXPECTED_COMMAND_FAILURES { - if error.is_instance(&class(py, module, name)?)? { + for failure in EXPECTED_COMMAND_FAILURES { + if error.is_instance(&host::entry(py, failure)?)? { + return Ok(Some(CommandFailure::Command)); + } + } + for native in EXPECTED_NATIVE_FAILURES { + if error.is_instance(®istry::error(py, native)?)? { return Ok(Some(CommandFailure::Command)); } } @@ -335,7 +320,7 @@ fn report_failure<'py>( let py: Python<'py> = invocation.py(); let (fallback_code, log_message) = failure_report(kind); log_failure(py, log_message, &error)?; - let user_error_class: Bound<'py, PyAny> = class(py, EXCEPTIONS_MODULE, "CliUserError")?; + let user_error_class: Bound<'py, PyAny> = registry::error(py, ErrorId::CliUser)?; let is_user_error: bool = error.is_instance(&user_error_class)?; let effective: Bound<'py, PyAny> = if is_user_error { write_diff_machine_error(invocation, &error)?.unwrap_or(error) @@ -343,7 +328,7 @@ fn report_failure<'py>( error }; write_compile_machine_error(invocation, &effective, fallback_code)?; - if effective.is_instance(&class(py, EXCEPTIONS_MODULE, "QueryDiffOutcomeError")?)? { + if effective.is_instance(®istry::error(py, ErrorId::QueryDiffOutcome)?)? { let line: Bound<'py, PyAny> = PyString::new(py, DIFF_OUTCOME_PREFIX) .into_any() .add(effective.getattr("status")?.str()?)?; @@ -367,13 +352,13 @@ fn report_skill_freshness(invocation: &Invocation<'_>, policy: InvocationPolicy) let py: Python<'_> = invocation.py(); let project_dir: Bound<'_, PyAny> = invocation.project_dir()?; let result: Bound<'_, PyAny> = match call_with( - &imported(py, SKILLS_MODULE, "maintain_sqlbuild_skills")?, + ®istry::native(py, NativeAttr::MaintainSqlbuildSkills)?, &[("project_dir", project_dir)], ) { Ok(result) => result, Err(error) => { let value: Bound<'_, PyAny> = error.value(py).clone().into_any(); - if value.is_instance(&class(py, EXCEPTIONS_MODULE, "CliUserError")?)? + if value.is_instance(®istry::error(py, ErrorId::CliUser)?)? || value.is_instance_of::() || value.is_instance_of::() { @@ -395,7 +380,7 @@ fn lifecycle_event<'py>( payload: Bound<'py, PyDict>, ) -> PyResult> { call_with( - &imported(py, LIFECYCLE_EVENT_MODULE, "create_lifecycle_event")?, + &host::entry(py, ExecutorHost::CreateLifecycleEvent)?, &[ ("event_type", PyString::new(py, event_type).into_any()), ("payload", payload.into_any()), @@ -467,13 +452,9 @@ fn handled_dispatch<'py>(invocation: &Invocation<'py>) -> PyResult> = observed_dispatch(invocation, policy); - imported( - py, - TRANSIENT_LINE_MODULE, - "shared_transient_line_coordinator", - )? - .call0()? - .call_method0("clear_active")?; + registry::native(py, NativeAttr::SharedTransientLineCoordinator)? + .call0()? + .call_method0("clear_active")?; let error: PyErr = match dispatched { Ok(exit_code) => return Ok(exit_code), Err(error) => error, @@ -498,10 +479,7 @@ fn external_context(py: Python<'_>) -> PyResult> { match invocation_context(py, None) { Ok(context) => Ok(context), Err(error) - if error.is_instance( - py, - &class(py, OUTPUT_CAPTURE_ERRORS_MODULE, "OutputCaptureInputError")?, - ) => + if error.is_instance(py, &host::entry(py, ExecutorHost::OutputCaptureInputError)?) => { Ok(PyDict::new(py).into_any()) } @@ -551,7 +529,7 @@ fn parsed_invocation<'py>( }, )?; call_with( - &imported(py, COMPUTE_LOGS_MODULE, "dispatch_with_compute_logs")?, + &host::entry(py, FacadeHost::DispatchWithComputeLogs)?, &[ ("args", args), ("identity", identity.clone()), @@ -604,19 +582,15 @@ fn run_cli<'py>( let argv: Bound<'py, PyAny> = offered_launch_record(&argv)?; let external: Bound<'py, PyAny> = external_context(py)?; let invocation_scope: Bound<'py, PyAny> = - imported(py, INVOCATION_SCOPE_MODULE, "invocation_scope")?.call0()?; + host::entry(py, ExecutorHost::InvocationScope)?.call0()?; within(&invocation_scope, |identity| { let external_scope: Bound<'py, PyAny> = call_with( - &imported( - py, - EXTERNAL_SCOPE_MODULE, - "invocation_external_context_scope", - )?, + &host::entry(py, ExecutorHost::InvocationExternalContextScope)?, &[("external_context", external.clone())], )?; within(&external_scope, |_| { let diagnostics: Bound<'py, PyAny> = call_with( - &imported(py, DIAGNOSTICS_MODULE, "diagnostics_context")?, + &host::entry(py, ExecutorHost::DiagnosticsContext)?, &[("sqlbuild_invocation_id", identity.getattr("invocation_id")?)], )?; within(&diagnostics, |_| { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cli/launch_state.rs b/crates/sqlbuild-python/src/bindings/_helpers/cli/launch_state.rs index be0bb290ed..78d618c392 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cli/launch_state.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cli/launch_state.rs @@ -3,9 +3,9 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyBool, PyDict, PyDictMethods}; -use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; /// The module attributes holding the offered path's and the withheld flag's `ContextVar`s. const OFFERED_ATTRIBUTE: &str = "_launch_record_offered"; const WITHHELD_ATTRIBUTE: &str = "_launch_record_withheld"; @@ -15,26 +15,26 @@ const WITHHELD_NAME: &str = "sqlbuild_launch_record_withheld"; /// Remember where `sqb` looks for this invocation's launch record, or that it offered none. pub(crate) fn offer_launch_record(py: Python<'_>, path: Option>) -> PyResult<()> { let path: Bound<'_, PyAny> = path.unwrap_or_else(|| py.None().into_bound(py)); - imported(py, NATIVE_MODULE, OFFERED_ATTRIBUTE)?.call_method1("set", (path,))?; - imported(py, NATIVE_MODULE, WITHHELD_ATTRIBUTE)?.call_method1("set", (false,))?; + registry::native(py, NativeAttr::LaunchRecordOffered)?.call_method1("set", (path,))?; + registry::native(py, NativeAttr::LaunchRecordWithheld)?.call_method1("set", (false,))?; Ok(()) } /// Withhold the launch record: `sqb` cannot replay what this invocation also depends on. pub(crate) fn withhold_launch_record(py: Python<'_>) -> PyResult<()> { - imported(py, NATIVE_MODULE, WITHHELD_ATTRIBUTE)?.call_method1("set", (true,))?; + registry::native(py, NativeAttr::LaunchRecordWithheld)?.call_method1("set", (true,))?; Ok(()) } /// The offered launch record path, unless an input outside its key was read. pub(crate) fn launch_record_path(py: Python<'_>) -> PyResult> { - if imported(py, NATIVE_MODULE, WITHHELD_ATTRIBUTE)? + if registry::native(py, NativeAttr::LaunchRecordWithheld)? .call_method0("get")? .is_truthy()? { return Ok(py.None().into_bound(py)); } - imported(py, NATIVE_MODULE, OFFERED_ATTRIBUTE)?.call_method0("get") + registry::native(py, NativeAttr::LaunchRecordOffered)?.call_method0("get") } fn context_variable<'py>( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cli/namespace.rs b/crates/sqlbuild-python/src/bindings/_helpers/cli/namespace.rs index d12c2955c8..777b09895a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cli/namespace.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cli/namespace.rs @@ -7,11 +7,8 @@ use pyo3::{IntoPyObjectExt, pyclass, pymethods}; use sqlbuild_cli::argv::main::cli_namespace_defaults::cli_namespace_defaults; use sqlbuild_cli::argv::models::NamespaceDefault; -use crate::bindings::_helpers::compile_driver::python_calls::imported; - -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const COMPILE_LINEAGE_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; /// Typed namespace for all CLI arguments across all commands. #[pyclass(module = "sqlbuild._native", dict, frozen)] @@ -26,10 +23,10 @@ fn default_object(py: Python<'_>, default: NamespaceDefault) -> PyResult Ok(PyList::empty(py).into_any()), NamespaceDefault::EmptyDict => Ok(PyDict::new(py).into_any()), NamespaceDefault::CompileLineageFast => { - imported(py, COMPILE_LINEAGE_MODULE, "CompileLineageMode")?.getattr("FAST") + registry::class(py, RecordId::CompileLineageMode)?.getattr("FAST") } NamespaceDefault::ColumnLineageRich => { - imported(py, NATIVE_RECORDS_MODULE, "ColumnLineageMode")?.getattr("RICH") + registry::class(py, RecordId::ColumnLineageMode)?.getattr("RICH") } } } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cli/observability.rs b/crates/sqlbuild-python/src/bindings/_helpers/cli/observability.rs index f90097dd99..798536cf2e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cli/observability.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cli/observability.rs @@ -15,16 +15,13 @@ use sqlbuild_core::event_export::constants::DEFAULT_EVENT_EXPORT_SHUTDOWN_TIMEOU use crate::bindings::_helpers::cli::launch_state::withhold_launch_record; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const EVENT_DISPATCHER_MODULE: &str = "sqlbuild.runtime.observability.classes.event_dispatcher"; -const DISPATCHER_SCOPE_MODULE: &str = "sqlbuild.runtime.observability.main.dispatcher_scope"; -const IDENTITY_MODULE: &str = "sqlbuild.runtime.observability.main.current_execution_identity"; -const EXTENSIONS_MODULE: &str = "sqlbuild.compiler.discovery.main.runtime_extensions"; -const OUTPUT_CAPTURE_MODULE: &str = - "sqlbuild.runtime.output_capture.main.configured_output_capture_scope"; -const DEBUG_EVENT_MODULE: &str = "sqlbuild.diagnostics.main.log_debug_event"; const OBSERVABILITY_LOGGER: &str = "sqlbuild.cli.observability"; const DEBUG_COMMAND: &str = "debug"; const LINEAGE_COMMAND: &str = "lineage"; @@ -46,10 +43,7 @@ fn log_debug<'py>( ("message", PyString::new(py, message).into_any()), ]; pairs.extend(fields.iter().cloned()); - call_with( - &imported(py, DEBUG_EVENT_MODULE, "log_debug_event")?, - &pairs, - )?; + call_with(&host::entry(py, ExecutorHost::LogDebugEvent)?, &pairs)?; Ok(()) } @@ -329,7 +323,7 @@ impl Observed { let mut command_output_sinks: bool = false; if args.getattr("command")?.ne(LINEAGE_COMMAND)? { let extensions: Bound<'_, PyAny> = call_with( - &imported(py, EXTENSIONS_MODULE, "discover_runtime_extensions")?, + &host::entry(py, UserCodeHost::DiscoverRuntimeExtensions)?, &[("project_dir", project_dir.clone())], )?; let event_exporters: Bound<'_, PyAny> = extensions.getattr("event_exporters")?; @@ -346,7 +340,7 @@ impl Observed { }; let timeout: Bound<'_, PyAny> = timeout.into_bound_py_any(py)?; let delivery: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "EventExporterDispatcher")?, + ®istry::native(py, NativeAttr::EventExporterDispatcher)?, &[ ("shutdown_timeout_seconds", timeout), ( @@ -360,7 +354,7 @@ impl Observed { ], )?; let scope: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "EventExporterCommandScope")?, + ®istry::native(py, NativeAttr::EventExporterCommandScope)?, &[("dispatcher", delivery.clone())], )?; self.exporter_scope = Some(scope.clone().unbind()); @@ -380,11 +374,11 @@ impl Observed { } } self.enter( - imported(py, DISPATCHER_SCOPE_MODULE, "dispatcher_scope")? - .call1((self.dispatcher.bind(py),))?, + host::entry(py, ExecutorHost::DispatcherScope)?.call1((self.dispatcher.bind(py),))?, )?; self.enter( - imported(py, NATIVE_MODULE, "terminal_event_index_scope")?.call1((terminal_index,))?, + registry::native(py, NativeAttr::TerminalEventIndexScopeFunction)? + .call1((terminal_index,))?, )?; let Some(scope) = self .exporter_scope @@ -393,12 +387,15 @@ impl Observed { else { return Ok(()); }; - self.enter(imported(py, NATIVE_MODULE, "event_exporter_command_scope")?.call1((&scope,))?)?; + self.enter( + registry::native(py, NativeAttr::EventExporterCommandScopeFunction)? + .call1((&scope,))?, + )?; let identity: Bound<'_, PyAny> = - imported(py, IDENTITY_MODULE, "current_execution_identity")?.call0()?; + host::entry(py, ExecutorHost::CurrentExecutionIdentity)?.call0()?; if !identity.is_none() && command_output_sinks { self.enter(call_with( - &imported(py, OUTPUT_CAPTURE_MODULE, "configured_output_capture_scope")?, + &host::entry(py, ExecutorHost::ConfiguredOutputCaptureScope)?, &[ ("exporter_scope", scope), ("identity", identity), @@ -434,14 +431,14 @@ impl CliObservabilityScope { fn __enter__(&self, py: Python<'_>) -> PyResult> { let args: &Bound<'_, PyAny> = self.args.bind(py); let dispatcher: Bound<'_, PyAny> = call_with( - &imported(py, EVENT_DISPATCHER_MODULE, "EventDispatcher")?, + &host::entry(py, ExecutorHost::EventDispatcher)?, &[( "health_callback", wrap_pyfunction!(log_dispatch_failure, py)?.into_any(), )], )?; let terminal_index: Bound<'_, PyAny> = - imported(py, NATIVE_MODULE, "TerminalEventIndex")?.call0()?; + registry::native(py, NativeAttr::TerminalEventIndex)?.call0()?; let subscribe = |subscriber: Bound<'_, PyAny>| -> PyResult> { Ok(call_with( &dispatcher.getattr("subscribe_lifecycle")?, @@ -456,7 +453,7 @@ impl CliObservabilityScope { .unbind()) }; let unsubscribe_terminal_index: Py = subscribe(terminal_index.getattr("consume")?)?; - let machine_output: bool = imported(py, NATIVE_MODULE, "machine_output_mode")? + let machine_output: bool = registry::native(py, NativeAttr::MachineOutputMode)? .call1((args,))? .is_truthy()?; let debug: Bound<'_, PyAny> = args.getattr("debug")?; @@ -466,7 +463,7 @@ impl CliObservabilityScope { && !args.getattr("no_color")?.is_truthy()? && stdout_supports_color(py)?; let projector: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "NativeProgressProjector")?, + ®istry::native(py, NativeAttr::NativeProgressProjector)?, &[ ( "stream", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cli/progress.rs b/crates/sqlbuild-python/src/bindings/_helpers/cli/progress.rs index 9348de724d..bf1bb54efb 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cli/progress.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cli/progress.rs @@ -17,13 +17,12 @@ use sqlbuild_core::progress::models::{ use sqlbuild_core::terminal_events::models::TerminalRequest; use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; /// The module attribute holding the `ContextVar` of the installed projector. const CONTEXT_ATTRIBUTE: &str = "_native_progress_projector_context"; const CONTEXT_NAME: &str = "sqlbuild_native_progress_projector"; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const COORDINATOR_MODULE: &str = "sqlbuild._native"; -const COORDINATOR_FUNCTION: &str = "shared_transient_line_coordinator"; const CLAIM_OPTIONS: (&[&str], &[&str]) = ( &["resource_name"], &["resource_id", "resource_attempt_id", "attempt_number"], @@ -134,9 +133,7 @@ fn progress_event(event: &Bound<'_, PyAny>) -> PyResult { /// The coordinator shared by every terminal writer in this process. pub(crate) fn shared_coordinator(py: Python<'_>) -> PyResult> { - py.import(COORDINATOR_MODULE)? - .getattr(COORDINATOR_FUNCTION)? - .call0() + registry::native(py, NativeAttr::SharedTransientLineCoordinator)?.call0() } fn persist(coordinator: &Bound<'_, PyAny>, stream: &Bound<'_, PyAny>, text: &str) -> PyResult<()> { @@ -312,7 +309,7 @@ impl NativeProgressProjector { } fn context_variable(py: Python<'_>) -> PyResult> { - py.import(NATIVE_MODULE)?.getattr(CONTEXT_ATTRIBUTE) + registry::native(py, NativeAttr::NativeProgressProjectorContext) } /// Return the projector installed for the current CLI invocation. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cli/progress_text.rs b/crates/sqlbuild-python/src/bindings/_helpers/cli/progress_text.rs index 6fb23a1220..a5e428ab38 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cli/progress_text.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cli/progress_text.rs @@ -16,9 +16,9 @@ use sqlbuild_core::progress::models::{ExpectationStep, InspectionRecord}; use sqlbuild_core::progress::types::DifferenceSample; use crate::bindings::_helpers::cli::progress::write_persistent; -use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported, within}; - -const INSPECTION_SINK_MODULE: &str = "sqlbuild.adapter.relations.main.inspection_query_sink"; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, within}; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; const HEADER_OPTIONS: (&[&str], &[&str]) = ( &["stream", "command", "target", "concurrency", "use_color"], @@ -174,8 +174,8 @@ pub(crate) fn with_verbose_inspection<'py, T>( } let writer: Bound<'py, VerboseInspectionWriter> = Bound::new(py, VerboseInspectionWriter::new())?; - let sink: Bound<'py, PyAny> = imported(py, INSPECTION_SINK_MODULE, "inspection_query_sink")? - .call1((writer.getattr("write")?,))?; + let sink: Bound<'py, PyAny> = + host::entry(py, AdapterHost::InspectionQuerySink)?.call1((writer.getattr("write")?,))?; let value: T = within(&sink, |_| body())?; writer.get().write_total(py)?; Ok(value) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_command.rs b/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_command.rs index dc63e2b38a..26b988e82b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_command.rs @@ -24,19 +24,13 @@ use crate::bindings::_helpers::clone::clone_execution::{ use crate::bindings::_helpers::clone::clone_pipeline::{ClonePipeline, prepared_clone_pipeline}; use crate::bindings::_helpers::clone::clone_progress::{clone_item_text, write_line}; use crate::bindings::_helpers::command_runtime::adapters::resolve_cli_adapter; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, within}; +use crate::bindings::_helpers::compile_driver::python_calls::{call_with, within}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ERRORS_MODULE: &str = "sqlbuild._native"; -const REJECT_UNIT_TESTS_MODULE: &str = "sqlbuild._native"; -const TARGET_NAME_MODULE: &str = "sqlbuild._native"; -const TARGET_CONFIG_MODULE: &str = "sqlbuild._native"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; -const EXTERNAL_REFS_MODULE: &str = "sqlbuild._native"; -const LIFECYCLE_MODULE: &str = "sqlbuild.runtime.observability.classes.operation_lifecycle"; const NO_DESTINATION: (&str, &str) = ( "C406", "clone requires --to when the project has no active target", @@ -60,7 +54,7 @@ fn user_error(py: Python<'_>, (code, message): (&str, &str)) -> PyErr { let raised = (|| { let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("code", code)?; - imported(py, ERRORS_MODULE, "CliUserError")?.call((message,), Some(&options)) + registry::error(py, ErrorId::CliUser)?.call((message,), Some(&options)) })(); match raised { Ok(error) => PyErr::from_value(error), @@ -84,12 +78,12 @@ fn progress_reporter<'py>( let options: Bound<'py, PyDict> = PyDict::new(py); options.set_item("stream", stream)?; options.set_item("use_color", style.use_color)?; - imported(py, NATIVE_MODULE, "PlanningProgressReporter")?.call((), Some(&options)) + registry::native(py, NativeAttr::PlanningProgressReporter)?.call((), Some(&options)) } fn lifecycle<'py>(py: Python<'py>, (kind, name): (&str, &str)) -> PyResult> { call_with( - &imported(py, LIFECYCLE_MODULE, "OperationLifecycle")?, + &host::entry(py, ExecutorHost::OperationLifecycle)?, &[ ("operation_kind", text(py, kind)), ("operation_name", text(py, name)), @@ -110,7 +104,7 @@ fn target_names(discovered: &Bound<'_, PyAny>) -> PyResult> { fn clone_policy(discovered: &Bound<'_, PyAny>, (target, flag): (&str, &str)) -> PyResult { let py: Python<'_> = discovered.py(); call_with( - &imported(py, TARGET_CONFIG_MODULE, "resolve_target_config")?, + ®istry::native(py, NativeAttr::ResolveTargetConfig)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), @@ -135,12 +129,12 @@ fn invocation<'py>(py: Python<'py>, request: &Bound<'py, PyAny>) -> PyResult = progress_reporter(&stream, style)?; progress.call_method1("start", ("Discovering project...",))?; let discovered: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[("project_dir", project_dir.clone())], )?; progress.call_method1("complete", ("Discovered project.",))?; call_with( - &imported(py, REJECT_UNIT_TESTS_MODULE, "reject_unit_test_selectors")?, + ®istry::native(py, NativeAttr::RejectUnitTestSelectors)?, &[ ("select", request.getattr("select")?), ("exclude", request.getattr("exclude")?), @@ -150,7 +144,7 @@ fn invocation<'py>(py: Python<'py>, request: &Bound<'py, PyAny>) -> PyResult = request.getattr("destination_target_name")?; if destination.is_none() { destination = call_with( - &imported(py, TARGET_NAME_MODULE, "resolve_target_name")?, + ®istry::native(py, NativeAttr::ResolveTargetName)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), @@ -172,7 +166,7 @@ fn invocation<'py>(py: Python<'py>, request: &Bound<'py, PyAny>) -> PyResult = call_with( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), @@ -197,7 +191,7 @@ fn connect<'py>( ) -> PyResult<(Bound<'py, PyAny>, Bound<'py, PyAny>)> { let py: Python<'py> = request.py(); let config: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "resolve_target_connection_config")?, + ®istry::native(py, NativeAttr::ResolveTargetConnectionConfig)?, &[ ("discovered_inputs", invocation.discovered.clone()), ("project_dir", invocation.project_dir.clone()), @@ -244,22 +238,18 @@ fn prepare<'py>( within(&lifecycle(py, ("project", "project_compile"))?, |_| { progress.call_method1("on_progress", ("Preparing clone plan...",))?; let resolver: Bound<'py, PyAny> = call_with( - &imported( - py, - EXTERNAL_REFS_MODULE, - "resolve_external_sql_reference_resolver", - )?, + ®istry::native(py, NativeAttr::ResolveExternalSqlReferenceResolver)?, &[ ("project_dir", invocation.project_dir.clone()), ("discovered_inputs", invocation.discovered.clone()), ], )?; let destination_connection: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "ClonePipelineConnection")?, + ®istry::class(py, RecordId::ClonePipelineConnection)?, &[("config", config.clone()), ("handle", connection.clone())], )?; let options: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "ClonePipelineOptions")?, + ®istry::class(py, RecordId::ClonePipelineOptions)?, &[ ("no_sql_validation", request.getattr("no_sql_validation")?), ("select", request.getattr("select")?), @@ -343,14 +333,14 @@ fn run_clone_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult = call_with( - &imported(py, NATIVE_MODULE, "format_clone_execution_json")?, + ®istry::native(py, NativeAttr::FormatCloneExecutionJson)?, &[ ("result", result.clone()), ("resource_types_by_name", resource_types.into_any()), ], )?; call_with( - &imported(py, NATIVE_MODULE, "write_execution_json_output")?, + ®istry::native(py, NativeAttr::WriteExecutionJsonOutput)?, &[ ("payload", payload), ("json_output", PyBool::new(py, false).to_owned().into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_execution.rs b/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_execution.rs index a0f6fd6b81..60cceba449 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_execution.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_execution.rs @@ -13,19 +13,12 @@ use crate::bindings::_helpers::clone::clone_progress::{ CloneFingerprintReporter, CloneItemPrinter, }; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, within}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS: &str = "sqlbuild._native"; const COMPILE_TYPES: &str = "sqlbuild.compiler.compile.types"; -const CLONE_MODELS: &str = "sqlbuild.executor.clone.models"; -const RETENTION_REQUESTS_MODULE: &str = "sqlbuild.executor.clone.main._build_retention_requests"; -const TRANSIENT_MODELS_MODULE: &str = - "sqlbuild.executor.clone.main._build_destination_transient_models"; -const FINGERPRINTING_MODULE: &str = "sqlbuild.executor.clone.main._fingerprinting"; -const EXECUTE_MODULE: &str = "sqlbuild.executor.clone.main.execute"; -const TARGET_CONFIG_MODULE: &str = "sqlbuild._native"; -const CONTRACT_TYPES: &str = "sqlbuild._native"; -const LIFECYCLE_MODULE: &str = "sqlbuild.runtime.observability.classes.operation_lifecycle"; const DESTINATION_ENTRY_GROUPS: [&str; 4] = [ "destination_source_entries", "destination_model_entries", @@ -121,9 +114,9 @@ fn dependency_locations<'py>( } else { plan.getattr("source_map")? }; - let key_class: Bound<'py, PyAny> = imported(py, COMPILE_MODELS, "CompiledObjectKey")?; + let key_class: Bound<'py, PyAny> = registry::class(py, RecordId::CompiledObjectKey)?; let location_class: Bound<'py, PyAny> = - imported(py, COMPILE_MODELS, "CompiledRelationLocation")?; + registry::class(py, RecordId::CompiledRelationLocation)?; let source_type: Bound<'py, PyAny> = imported(py, COMPILE_TYPES, "CompiledResourceType")?.getattr("SOURCE")?; for pair in source_map.call_method0("items")?.try_iter()? { @@ -157,7 +150,7 @@ fn dependency_locations<'py>( fn target_config<'py>(invocation: &Invocation<'py>) -> PyResult> { let py: Python<'py> = invocation.adapter.py(); call_with( - &imported(py, TARGET_CONFIG_MODULE, "resolve_target_config")?, + ®istry::native(py, NativeAttr::ResolveTargetConfig)?, &[ ( "project_config", @@ -191,11 +184,7 @@ fn execution_input<'py>( .map(|entry| entry?.getattr("name")) .collect::>()?; let retention: Bound<'py, PyAny> = call_with( - &imported( - py, - RETENTION_REQUESTS_MODULE, - "build_destination_retention_requests", - )?, + &host::entry(py, ExecutorHost::BuildDestinationRetentionRequests)?, &[ ("project", project.clone()), ( @@ -214,9 +203,9 @@ fn execution_input<'py>( )?; let allow_decrease: bool = config .getattr("time_travel_retention_decrease")? - .eq(imported(py, CONTRACT_TYPES, "RetentionDecreasePolicy")?.getattr("ALLOW")?)?; + .eq(registry::class(py, RecordId::RetentionDecreasePolicy)?.getattr("ALLOW")?)?; let source_entries: Bound<'py, PyAny> = call_with( - &imported(py, CLONE_MODELS, "CloneSourceEntries")?, + &host::entry(py, ExecutorHost::CloneSourceEntries)?, &[ ("origin", pipeline.getattr("origin_source_entries")?), ( @@ -226,11 +215,7 @@ fn execution_input<'py>( ], )?; let transient: Bound<'py, PyAny> = call_with( - &imported( - py, - TRANSIENT_MODELS_MODULE, - "build_destination_transient_models", - )?, + &host::entry(py, ExecutorHost::BuildDestinationTransientModels)?, &[("project", project.clone())], )?; let mut fields: Vec<(&str, Bound<'py, PyAny>)> = vec![("source_entries", source_entries)]; @@ -268,7 +253,10 @@ fn execution_input<'py>( ), ("on_item", on_item), ]); - call_with(&imported(py, CLONE_MODELS, "CloneExecutionInput")?, &fields) + call_with( + &host::entry(py, ExecutorHost::CloneExecutionInput)?, + &fields, + ) } /// Copy fingerprints of the cloned relations, reporting what was not confirmed on failure. @@ -309,7 +297,7 @@ fn copy_fingerprints<'py>( ("on_progress", reporter.clone().into_any()), ]); let lifecycle: Bound<'py, PyAny> = call_with( - &imported(py, LIFECYCLE_MODULE, "OperationLifecycle")?, + &host::entry(py, ExecutorHost::OperationLifecycle)?, &[ ("operation_kind", PyString::new(py, "clone").into_any()), ( @@ -320,7 +308,7 @@ fn copy_fingerprints<'py>( )?; let copied: PyResult> = within(&lifecycle, |_| { call_with( - &imported(py, FINGERPRINTING_MODULE, "copy_clone_fingerprints")?, + &host::entry(py, ExecutorHost::CopyCloneFingerprints)?, &fields, ) }); @@ -340,7 +328,7 @@ pub(crate) fn execute_clone_plan<'py>( let py: Python<'py> = pipeline.py(); let started: Instant = Instant::now(); let event_writer: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "ExecutionEventWriter")?, + ®istry::native(py, NativeAttr::ExecutionEventWriter)?, &[("path", request.getattr("event_output_path")?)], )?; let printer: Py = Py::new( @@ -363,7 +351,7 @@ pub(crate) fn execute_clone_plan<'py>( ), )?; call_with( - &imported(py, EXECUTE_MODULE, "execute_clone")?, + &host::entry(py, ExecutorHost::ExecuteClone)?, &[("inputs", input)], ) })(); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_pipeline.rs b/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_pipeline.rs index ffe3319388..4ce45f6f2b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_pipeline.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_pipeline.rs @@ -5,13 +5,11 @@ use pyo3::types::{PyDict, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::boundary::keyword_request::Request; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::contracts::spec_resolution::resolve_target_config; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const CLONE_PLANNING_MODULE: &str = "sqlbuild._native"; /// The `ClonePipelineResult` fields after the two projects, in `run_clone_planning` order. const PLANNED_FIELDS: [&str; 8] = [ "clone_plan", @@ -41,7 +39,7 @@ fn compiled_environment<'py>( let py: Python<'py> = target_name.py(); let options: &Bound<'py, PyAny> = &pipeline.options; call_with( - &imported(py, NATIVE_MODULE, "build_compiled_project")?, + ®istry::native(py, NativeAttr::BuildCompiledProject)?, &[ ("discovered_inputs", pipeline.discovered.clone()), ("adapter", pipeline.adapter.clone()), @@ -54,7 +52,7 @@ fn compiled_environment<'py>( ( "analysis_selection", call_with( - &imported(py, NATIVE_RECORDS_MODULE, "CompileAnalysisSelection")?, + ®istry::class(py, RecordId::CompileAnalysisSelection)?, &[("no_cache", options.getattr("no_cache")?)], )?, ), @@ -110,7 +108,7 @@ pub(crate) fn prepared_clone_pipeline<'py>( let destination_source_project: Bound<'py, PyAny> = source_project(pipeline, &destination_project, destination_target)?; let planned: Bound<'py, PyTuple> = call_with( - &imported(py, CLONE_PLANNING_MODULE, "run_clone_planning")?, + ®istry::native(py, NativeAttr::RunClonePlanning)?, &[ ("project", destination_project.clone()), ("select", pipeline.options.getattr("select")?), @@ -131,7 +129,7 @@ pub(crate) fn prepared_clone_pipeline<'py>( fields.push((*name, planned.get_item(index)?)); } call_with( - &imported(py, NATIVE_RECORDS_MODULE, "ClonePipelineResult")?, + ®istry::class(py, RecordId::ClonePipelineResult)?, &fields, ) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/adapters.rs b/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/adapters.rs index 3edb6eb057..e367ed907b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/adapters.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/adapters.rs @@ -6,11 +6,12 @@ use pyo3::prelude::{ use pyo3::types::{PyDict, PyDictMethods}; use pyo3::{pyfunction, wrap_pyfunction}; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; -const REGISTRY_MODULE: &str = "sqlbuild.adapter.discovery.main.resolve_adapter"; -const ADAPTER_ERRORS_MODULE: &str = "sqlbuild.adapter.contract.exceptions"; -const CLI_ERRORS_MODULE: &str = "sqlbuild._native"; const ADAPTER_CONFIGURATION_CODE: &str = "C601"; /// `CliUserError(error.message, code="C601", help=error.help)` raised from `error`. @@ -20,7 +21,7 @@ fn cli_adapter_error(py: Python<'_>, error: &PyErr) -> PyResult { keywords.set_item("code", ADAPTER_CONFIGURATION_CODE)?; keywords.set_item("help", value.getattr("help")?)?; let raised: PyErr = PyErr::from_value( - imported(py, CLI_ERRORS_MODULE, "CliUserError")? + registry::error(py, ErrorId::CliUser)? .call((value.getattr("message")?,), Some(&keywords))?, ); raised.set_cause(py, Some(error.clone_ref(py))); @@ -34,19 +35,14 @@ pub(crate) fn resolve_cli_adapter<'py>( ) -> PyResult> { let py: Python<'py> = adapter_name.py(); let resolved: PyResult> = call_with( - &imported(py, REGISTRY_MODULE, "resolve_adapter")?, + &host::entry(py, AdapterHost::ResolveAdapter)?, &[ ("adapter_name", adapter_name.clone()), ("project_dir", project_dir.clone()), ], ); match resolved { - Err(error) - if error.is_instance( - py, - &imported(py, ADAPTER_ERRORS_MODULE, "AdapterUserError")?, - ) => - { + Err(error) if error.is_instance(py, &host::entry(py, AdapterHost::AdapterUserError)?) => { Err(cli_adapter_error(py, &error)?) } resolved => resolved, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/connection.rs b/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/connection.rs index 0530f4ec01..2ccae8393e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/connection.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/connection.rs @@ -18,13 +18,11 @@ use crate::bindings::_helpers::command_runtime::warehouses::{WarehouseInputs, ap use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, }; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::third_party::ThirdPartyHost; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; -const DBT_PROFILE_MODULE: &str = "sqlbuild._native"; -const DBT_ERRORS_MODULE: &str = "sqlbuild.integrations.dbt.exceptions"; -const EFFECTIVE_CONFIG_MODULE: &str = "sqlbuild._native"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; -const TARGET_NAME_MODULE: &str = "sqlbuild._native"; -const TARGET_CONFIG_MODULE: &str = "sqlbuild._native"; const CONNECTION_OPTIONS: (&[&str], &[&str]) = ( &["raw_config", "project_dir", "adapter_name"], &["discovered_inputs", "cli_vars", "selected_target"], @@ -52,7 +50,7 @@ fn warn(py: Python<'_>, message: &str) -> PyResult<()> { } fn dbt_profile_error(py: Python<'_>) -> PyErr { - match imported(py, DBT_ERRORS_MODULE, "DbtProfileError") + match host::entry(py, ThirdPartyHost::DbtProfileError) .and_then(|class| class.call1((DBT_PROFILE_REQUIRES_PROJECT,))) { Ok(instance) => PyErr::from_value(instance), @@ -69,7 +67,7 @@ pub(crate) fn with_dbt_profile<'py>( ) -> PyResult> { let py: Python<'py> = raw_config.py(); let profile: Bound<'py, PyAny> = call_with( - &imported(py, DBT_PROFILE_MODULE, "resolve_raw_dbt_profile_connection")?, + ®istry::native(py, NativeAttr::ResolveRawDbtProfileConnection)?, &[ ("raw_config", config.clone().into_any()), ("project_dir", project_dir.clone()), @@ -208,7 +206,7 @@ fn require_configured_connection( return Ok(()); } let target_name: Bound<'_, PyAny> = call_with( - &imported(py, TARGET_NAME_MODULE, "resolve_target_name")?, + ®istry::native(py, NativeAttr::ResolveTargetName)?, &[ ("project_config", project_config.clone()), ("local_config", local_config.clone()), @@ -223,7 +221,7 @@ fn require_configured_connection( return Err(raised(py, missing_connection_error(&project_file, None))); } let target_config: Bound<'_, PyAny> = call_with( - &imported(py, TARGET_CONFIG_MODULE, "resolve_target_config")?, + ®istry::native(py, NativeAttr::ResolveTargetConfig)?, &[ ("project_config", project_config), ("local_config", local_config), @@ -252,11 +250,7 @@ pub(crate) fn project_connection_config<'py>( require_configured_connection(discovered_inputs, project_dir, selected_target)?; } let raw_config: Bound<'py, PyDict> = call_with( - &imported( - py, - EFFECTIVE_CONFIG_MODULE, - "build_effective_connection_config", - )?, + ®istry::native(py, NativeAttr::BuildEffectiveConnectionConfig)?, &[ ("discovered_inputs", discovered_inputs.clone()), ("selected_target", selected_target.clone()), @@ -265,7 +259,7 @@ pub(crate) fn project_connection_config<'py>( )? .cast_into()?; let adapter_name: Bound<'py, PyAny> = call_with( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ( "project_config", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/invocation.rs b/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/invocation.rs index 9d52b3acc5..cb825263ab 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/invocation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/invocation.rs @@ -6,8 +6,8 @@ use pyo3::types::PyBool; use crate::bindings::_helpers::command_runtime::adapters::resolve_cli_adapter; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; - -const NATIVE_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; /// The project directory, discovered inputs, adapter context, colour and progress stream. type InvocationContext<'py> = ( @@ -31,7 +31,7 @@ pub(crate) fn resolve_adapter_connection_context<'py>( ) -> PyResult> { let py: Python<'py> = discovered.py(); let adapter_name: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), @@ -39,7 +39,7 @@ pub(crate) fn resolve_adapter_connection_context<'py>( )?; let adapter: Bound<'py, PyAny> = resolve_cli_adapter(&adapter_name, project_dir)?; let connection_config: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "resolve_project_connection_config")?, + ®istry::native(py, NativeAttr::ResolveProjectConnectionConfig)?, &[ ("discovered_inputs", discovered.clone()), ("project_dir", project_dir.clone()), @@ -49,7 +49,7 @@ pub(crate) fn resolve_adapter_connection_context<'py>( ], )?; call_with( - &imported(py, NATIVE_MODULE, "AdapterConnectionContext")?, + ®istry::class(py, RecordId::AdapterConnectionContext)?, &[ ("adapter_name", adapter_name), ("adapter", adapter), @@ -70,7 +70,7 @@ fn invocation_context<'py>( requested }; let discovered: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[("project_dir", project_dir.clone())], )?; let adapter_context: Bound<'py, PyAny> = resolve_adapter_connection_context( @@ -132,7 +132,7 @@ pub(crate) fn resolve_reported_command_invocation<'py>( fields.push(( "connection_progress", call_with( - &imported(py, NATIVE_MODULE, "ConnectionProgressReporter")?, + ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, &[ ("adapter_name", adapter_context.getattr("adapter_name")?), ("stream", stream.clone()), @@ -143,7 +143,7 @@ pub(crate) fn resolve_reported_command_invocation<'py>( fields.push(( "planning_progress", call_with( - &imported(py, NATIVE_MODULE, "PlanningProgressReporter")?, + ®istry::native(py, NativeAttr::PlanningProgressReporter)?, &[ ("stream", stream.clone()), ("use_color", boolean(py, *use_color)), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/runtime_errors.rs b/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/runtime_errors.rs index 33da4de19c..37e5a2be94 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/runtime_errors.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/runtime_errors.rs @@ -4,11 +4,8 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods}; use sqlbuild_config::command_connection::errors::CommandConnectionError; -use crate::bindings::_helpers::compile_driver::python_calls::imported; - -const CLI_ERRORS_MODULE: &str = "sqlbuild._native"; -const DISCOVERY_ERRORS_MODULE: &str = "sqlbuild._native"; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; /// The `CliUserError` or `ProjectConfigError` for `error`. pub(crate) fn raised(py: Python<'_>, error: CommandConnectionError) -> PyErr { @@ -22,11 +19,10 @@ pub(crate) fn raised(py: Python<'_>, error: CommandConnectionError) -> PyErr { let keywords: Bound<'_, PyDict> = PyDict::new(py); keywords.set_item("code", code)?; keywords.set_item("help", help)?; - imported(py, CLI_ERRORS_MODULE, "CliUserError")? - .call((message,), Some(&keywords))? + registry::error(py, ErrorId::CliUser)?.call((message,), Some(&keywords))? } CommandConnectionError::ProjectConfig(message) => { - imported(py, DISCOVERY_ERRORS_MODULE, "ProjectConfigError")?.call1((message,))? + registry::error(py, ErrorId::ProjectConfig)?.call1((message,))? } }; Ok(PyErr::from_value(instance)) @@ -37,18 +33,15 @@ pub(crate) fn raised(py: Python<'_>, error: CommandConnectionError) -> PyErr { /// Whether `error` is a `CompileInputError` or a `CliUserError`, the failures resolution reports. pub(crate) fn expected_resolution_error(py: Python<'_>, error: &PyErr) -> PyResult { Ok( - error.is_instance(py, &imported(py, CLI_ERRORS_MODULE, "CliUserError")?) - || error.is_instance( - py, - &imported(py, COMPILE_ERRORS_MODULE, "CompileInputError")?, - ), + error.is_instance(py, ®istry::error(py, ErrorId::CliUser)?) + || error.is_instance(py, ®istry::error(py, ErrorId::CompileInput)?), ) } /// The message a resolution error shows: `CliUserError.message`, else `str(error)`. pub(crate) fn resolution_message(py: Python<'_>, error: &PyErr) -> PyResult { let value: Bound<'_, PyAny> = error.value(py).clone().into_any(); - if error.is_instance(py, &imported(py, CLI_ERRORS_MODULE, "CliUserError")?) { + if error.is_instance(py, ®istry::error(py, ErrorId::CliUser)?) { return value.getattr("message")?.str()?.extract(); } value.str()?.extract() diff --git a/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/warehouse_scope.rs b/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/warehouse_scope.rs index 25cd0771dd..4806e45ac7 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/warehouse_scope.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/warehouse_scope.rs @@ -9,17 +9,15 @@ use pyo3::{pyclass, pyfunction, pymethods, wrap_pyfunction}; use sqlbuild_config::command_connection::constants::COMMAND_WAREHOUSE_GROUPS; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ENTRY_MODELS: &str = "sqlbuild._native"; -const WAREHOUSE_GROUP: (&str, &str) = ("sqlbuild._native", "WarehouseGroup"); -const COMMAND_TYPES: &str = "sqlbuild._native"; const CONTEXT_ATTRIBUTE: &str = "_command_warehouse_scope_context"; const CONTEXT_NAME: &str = "sqlbuild_command_warehouse_scope"; fn context_variable(py: Python<'_>) -> PyResult> { - py.import(NATIVE_MODULE)?.getattr(CONTEXT_ATTRIBUTE) + registry::native(py, NativeAttr::CommandWarehouseScopeContext) } /// Installs one command's warehouse scope for the duration of a `with` block. @@ -67,7 +65,7 @@ pub(crate) fn command_warehouse_scope<'py>( let group: Bound<'py, PyAny> = if command.is_none() { py.None().into_bound(py) } else { - let name: String = imported(py, COMMAND_TYPES, "CliCommand")? + let name: String = registry::class(py, RecordId::CliCommand)? .call1((command,))? .str()? .to_string(); @@ -76,7 +74,7 @@ pub(crate) fn command_warehouse_scope<'py>( .find(|(candidate, _)| *candidate == name) .and_then(|(_, group)| *group) { - Some(group) => imported(py, WAREHOUSE_GROUP.0, WAREHOUSE_GROUP.1)?.call1((group,))?, + Some(group) => registry::class(py, RecordId::WarehouseGroup)?.call1((group,))?, None => py.None().into_bound(py), } }; @@ -85,7 +83,7 @@ pub(crate) fn command_warehouse_scope<'py>( None => PyDict::new(py).into_any(), }; let scope: Bound<'py, PyAny> = call_with( - &imported(py, ENTRY_MODELS, "CommandWarehouseScope")?, + ®istry::class(py, RecordId::CommandWarehouseScope)?, &[ ("group", group), ("cli_warehouse", cli_warehouse.clone()), @@ -106,7 +104,7 @@ pub(crate) fn current_command_warehouse_scope(py: Python<'_>) -> PyResult( source: &str, ) -> PyResult> { call_with( - &imported(py, ENTRY_MODELS, "ResolvedWarehouse")?, + ®istry::class(py, RecordId::ResolvedWarehouse)?, &[ ( "warehouse", @@ -140,7 +137,7 @@ pub(crate) fn find_authored<'py>( let project_config: Bound<'py, PyAny> = inputs.discovered_inputs.getattr("project_config")?; let local_config: Bound<'py, PyAny> = inputs.discovered_inputs.getattr("local_config")?; let target_name: Bound<'py, PyAny> = call_with( - &imported(py, TARGET_NAME_MODULE, "resolve_target_name")?, + ®istry::native(py, NativeAttr::ResolveTargetName)?, &[ ("project_config", project_config.clone()), ("local_config", local_config.clone()), @@ -177,11 +174,7 @@ fn expand_authored( ) -> PyResult { let py: Python<'_> = found.group.py(); let runtime: Bound<'_, PyAny> = call_with( - &imported( - py, - EFFECTIVE_RUNTIME_MODULE, - "build_effective_runtime_config", - )?, + ®istry::native(py, NativeAttr::BuildEffectiveRuntimeConfig)?, &[ ("discovered_inputs", inputs.discovered_inputs.clone()), ("selected_target", inputs.selected_target.clone()), @@ -221,11 +214,7 @@ fn session_warehouse_support( project_dir: &Bound<'_, PyAny>, ) -> PyResult { call_with( - &imported( - py, - SESSION_WAREHOUSE_MODULE, - "adapter_supports_session_warehouse", - )?, + &host::entry(py, AdapterHost::AdapterSupportsSessionWarehouse)?, &[ ("adapter_name", adapter_name.clone()), ("project_dir", project_dir.clone()), @@ -296,7 +285,7 @@ fn validate_cli_warehouse( checked_cli_warehouse(py, &cli_warehouse)?; let discovered: Bound<'_, PyAny> = discover_project_configuration(py, &project_dir)?; let adapter_name: Bound<'_, PyAny> = call_with( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/cursor_roles.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/cursor_roles.rs index ebd1627a41..fd1f70a8ff 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/cursor_roles.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/cursor_roles.rs @@ -4,9 +4,9 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::pyfunction; use pyo3::types::{PyDict, PyDictMethods, PyList, PyListMethods, PyString}; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; - -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; const CURSOR_INPUTS_KEY: &str = "cursor_inputs"; const FILTER_ROLE: &str = "filter"; @@ -94,7 +94,7 @@ pub(crate) fn resolve_cursor_input_roles<'py>( } } call_with( - &imported(py, NATIVE_RECORDS_MODULE, "CursorInputRoles")?, + ®istry::class(py, RecordId::CursorInputRoles)?, &[ ("filter_inputs", filter.into_any()), ("watermark_inputs", watermark.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/declaration_scope.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/declaration_scope.rs index 30b2b65cc1..725d880c7f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/declaration_scope.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/declaration_scope.rs @@ -5,22 +5,17 @@ use pyo3::pyfunction; use pyo3::types::PyTuple; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::render::declaration_values::declaration_scope_resolver; use crate::bindings::_helpers::scopes::scope_index::open_native_scope_index; use crate::bindings::_helpers::scopes::scope_lookup::build_scope_lookup; use crate::bindings::_helpers::scopes::scope_validation::validate_scope_index; use crate::bindings::_helpers::sql_test_glue::constants::SQL_TEST_EXPECTED_MODELS_SCAN_ALGORITHM; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const SCAN_CACHE_MODULE: &str = "sqlbuild._native"; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; -const SCOPE_ERRORS_MODULE: &str = "sqlbuild._native"; - fn compile_error(py: Python<'_>, message: Bound<'_, PyAny>) -> PyResult { Ok(PyErr::from_value( - imported(py, COMPILE_ERRORS_MODULE, "CompileInputError")?.call1((message,))?, + registry::error(py, ErrorId::CompileInput)?.call1((message,))?, )) } @@ -34,7 +29,7 @@ fn scope_build<'py>( let resolver: Bound<'py, PyAny> = declaration_scope_resolver(discovered, &index, (loaded_macros, &lookup))?; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DeclarationScopeBuild")?, + ®istry::class(py, RecordId::DeclarationScopeBuild)?, &[ ("loaded_macros", loaded_macros.clone()), ("index", index), @@ -55,7 +50,7 @@ pub(crate) fn build_declaration_scope<'py>( ) -> PyResult> { let py: Python<'py> = discovered_inputs.py(); let scan_cache: Bound<'py, PyAny> = call_with( - &imported(py, SCAN_CACHE_MODULE, "SqlTestScanCache")?, + ®istry::native(py, NativeAttr::SqlTestScanCache)?, &[( "cache_dir", compile_cache_dir.unwrap_or_else(|| py.None().into_bound(py)), @@ -63,7 +58,7 @@ pub(crate) fn build_declaration_scope<'py>( )?; let test_files: Bound<'py, PyAny> = discovered_inputs.getattr("test_files")?; let scenario_files: Bound<'py, PyAny> = discovered_inputs.getattr("scenario_files")?; - imported(py, NATIVE_MODULE, "validate_authored_sql_test_ctes")?.call1(( + registry::native(py, NativeAttr::ValidateAuthoredSqlTestCtes)?.call1(( &test_files, &scenario_files, sql_lexical_syntax.getattr("native_mapping")?, @@ -75,10 +70,7 @@ pub(crate) fn build_declaration_scope<'py>( .into_any(); let validated: Result<(), PyErr> = validate_scope_index(&native.getattr("index")?); if let Err(error) = validated { - if !error.is_instance( - py, - &imported(py, SCOPE_ERRORS_MODULE, "ScopeValidationError")?, - ) { + if !error.is_instance(py, ®istry::error(py, ErrorId::ScopeValidation)?) { return Err(error); } let raised: PyErr = compile_error(py, error.value(py).str()?.into_any())?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/effective_config.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/effective_config.rs index 0a524f17fa..174e0e20b9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/effective_config.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/effective_config.rs @@ -13,10 +13,9 @@ use crate::bindings::_helpers::contracts::spec_resolution::{ use crate::bindings::_helpers::model_config::template_data::{ expanded_effective_vars, expanded_template_data, run_id, }; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; -const CONTRACT_MODELS_MODULE: &str = "sqlbuild._native"; const CONNECTION_LABEL: &str = "effective connection"; /// Merge base project connection with the selected environment overrides. @@ -30,12 +29,12 @@ pub(crate) fn build_effective_connection<'py>( ) -> PyResult> { let py: Python<'py> = project_config.py(); let (connection, error): (Bound<'py, PyAny>, Option) = - imported(py, NATIVE_MODULE, "effective_config_connection")? + registry::native(py, NativeAttr::EffectiveConfigConnection)? .call1((project_config, local_config, target_config))? .extract()?; if connection.is_none() { - let raised: Bound<'py, PyAny> = imported(py, COMPILE_ERRORS_MODULE, "CompileInputError")? - .call1((error.unwrap_or_default(),))?; + let raised: Bound<'py, PyAny> = + registry::error(py, ErrorId::CompileInput)?.call1((error.unwrap_or_default(),))?; return Err(PyErr::from_value(raised)); } expanded_template_data( @@ -60,13 +59,13 @@ pub(crate) fn build_effective_settings<'py>( local_config: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = project_config.py(); - let settings_class: Bound<'py, PyAny> = imported(py, CONTRACT_MODELS_MODULE, "SettingsConfig")?; + let settings_class: Bound<'py, PyAny> = registry::class(py, RecordId::SettingsConfig)?; let names: Vec> = imported(py, "dataclasses", "fields")? .call1((&settings_class,))? .try_iter()? .map(|field| field?.getattr("name")) .collect::>()?; - let values: Bound<'py, PyDict> = imported(py, NATIVE_MODULE, "effective_config_settings")? + let values: Bound<'py, PyDict> = registry::native(py, NativeAttr::EffectiveConfigSettings)? .call1(( ( project_config.getattr("settings")?, @@ -89,7 +88,7 @@ pub(crate) fn build_effective_vars<'py>( cli_vars: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = project_config.py(); - let merged: Bound<'py, PyAny> = imported(py, NATIVE_MODULE, "effective_config_vars")? + let merged: Bound<'py, PyAny> = registry::native(py, NativeAttr::EffectiveConfigVars)? .call1(((project_config, local_config), target_config, cli_vars))?; expanded_effective_vars(merged.downcast()?) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/model_config.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/model_config.rs index 42bf143684..6f2fcef8ae 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/model_config.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/model_config.rs @@ -1,20 +1,15 @@ //! The native config builder, validator and inherited storage policies of one compile. -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class_at; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::pyfunction; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; use crate::bindings::_helpers::compile_attachment::storage_policies::inherited_storage; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::compiled_project::loop_classes::table_backed; use crate::bindings::_helpers::model_config::unicode_environment::unicode_environment; - -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const DISCOVERY_MODELS_MODULE: &str = "sqlbuild._native"; -const CONTRACT_MODELS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; /// Capture the project inputs every model's native config build and validation read. #[pyfunction] @@ -30,21 +25,25 @@ pub(crate) fn native_model_config_session<'py>( let materialization_defaults: Bound<'py, PyAny> = if declared.is_truthy()? { declared } else { - imported(py, CONTRACT_MODELS_MODULE, "MaterializationDefaultsConfig")?.call0()? + registry::class(py, RecordId::MaterializationDefaultsConfig)?.call0()? }; let hook_entries: Bound<'py, PyTuple> = PyTuple::new( py, - ["SqlHookEntry", "NamedSqlHookEntry", "PythonHookEntry"] - .iter() - .map(|name| shape_class_at(py, DISCOVERY_MODELS_MODULE, name)) - .collect::>>()?, + [ + RecordId::SqlHookEntry, + RecordId::NamedSqlHookEntry, + RecordId::PythonHookEntry, + ] + .iter() + .map(|class| registry::class(py, *class)) + .collect::>>()?, )?; let target_namespace: Bound<'py, PyAny> = if target.is_none() { target.clone() } else { PyTuple::new(py, [target.getattr("database")?, target.getattr("schema")?])?.into_any() }; - let builder: Bound<'py, PyAny> = imported(py, NATIVE_MODULE, "NativeModelConfigBuilder")? + let builder: Bound<'py, PyAny> = registry::native(py, NativeAttr::NativeModelConfigBuilder)? .call1(( ( project_config.getattr("defaults")?, @@ -69,7 +68,7 @@ pub(crate) fn native_model_config_session<'py>( inherited.set_item(&materialized, storage)?; } call_with( - &imported(py, NATIVE_RECORDS_MODULE, "NativeModelConfigSession")?, + ®istry::class(py, RecordId::NativeModelConfigSession)?, &[ ("path_defaults", project_config.getattr("path_defaults")?), ("builder", builder), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/model_inputs.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/model_inputs.rs index a8a4f3bac9..96063ddc63 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/model_inputs.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/model_inputs.rs @@ -16,28 +16,18 @@ use crate::bindings::_helpers::model_config::unicode_environment::unicode_enviro use crate::bindings::_helpers::render::declaration_values::constant_declaration_sql; use crate::bindings::_helpers::render::macro_expansion_result::macro_expansion_classes; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; -const DISCOVERY_MODELS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use sqlbuild_core::constants::PROJECT_CONFIG_FILENAME; -const SCOPE_MODELS_MODULE: &str = "sqlbuild._native"; -const SCOPE_TYPES_MODULE: &str = "sqlbuild._native"; -const COLLECTOR_MODULE: &str = "sqlbuild._native"; -const PROJECT_VARS_MODULE: &str = "sqlbuild._native"; -const MACRO_BRIDGE_MODULE: &str = "sqlbuild.compiler.macro_bridge.main.active_macro_bridge"; -const SQL_ANALYSIS_MODELS_MODULE: &str = "sqlbuild._native"; -const PYTHON_NODE_MODELS_MODULE: &str = "sqlbuild.python_nodes.models"; -const PYTHON_NODE_TYPES_MODULE: &str = "sqlbuild.python_nodes.types"; /// The explicit reference call kinds and the `SqlResourceRefKind` member each resolves to. const EXPLICIT_REFERENCE_KINDS: [(&str, &str); 3] = [("ref", "MODEL"), ("source", "SOURCE"), ("seed", "SEED")]; /// The explicit reference call kinds mapped to their `SqlResourceRefKind` members. fn explicit_reference_kinds(py: Python<'_>) -> PyResult> { - let kinds: Bound<'_, PyAny> = imported(py, PYTHON_NODE_TYPES_MODULE, "SqlResourceRefKind")?; + let kinds: Bound<'_, PyAny> = host::entry(py, UserCodeHost::SqlResourceRefKind)?; let built: Bound<'_, PyDict> = PyDict::new(py); for (call_kind, member) in EXPLICIT_REFERENCE_KINDS { built.set_item(call_kind, kinds.getattr(member)?)?; @@ -97,7 +87,7 @@ fn macro_inputs<'py>(context: &Bound<'py, PyAny>) -> PyResult ("macro_context", context.getattr("macro_context")?), ( "bridge", - imported(py, MACRO_BRIDGE_MODULE, "active_macro_bridge")?.call0()?, + host::entry(py, UserCodeHost::ActiveMacroBridge)?.call0()?, ), ( "scoped", @@ -107,16 +97,16 @@ fn macro_inputs<'py>(context: &Bound<'py, PyAny>) -> PyResult ), ( "declaration_context", - imported(py, COMPILE_MODELS_MODULE, "DeclarationResolutionContext")?, + registry::class(py, RecordId::DeclarationResolutionContext)?, ), ( "macro_argument_error", - imported(py, COMPILE_ERRORS_MODULE, "MacroArgumentError")?, + registry::error(py, ErrorId::MacroArgument)?, ), ("expansion", macro_expansion_classes(py)?.into_any()), ( "generic_syntax", - imported(py, SQL_ANALYSIS_MODELS_MODULE, "SqlLexicalSyntax")? + registry::class(py, RecordId::SqlLexicalSyntax)? .call0()? .getattr("native_mapping")?, ), @@ -134,31 +124,31 @@ fn sql_hook_inputs<'py>( vec![ ( "named_sql_hook_entry", - imported(py, NATIVE_RECORDS_MODULE, "NamedSqlHookEntry")?, + registry::class(py, RecordId::NamedSqlHookEntry)?, ), ( "python_hook_entry", - imported(py, DISCOVERY_MODELS_MODULE, "PythonHookEntry")?, + registry::class(py, RecordId::PythonHookEntry)?, ), ("hook_files", discovered.getattr("sql_hook_files")?), ("effective_vars", context.getattr("effective_vars")?), ("environment", unicode_environment(py)?), ( "render_project_var", - imported(py, PROJECT_VARS_MODULE, "render_project_var_text")?, + registry::native(py, NativeAttr::RenderProjectVarText)?, ), ( "sql_resource_ref", - imported(py, PYTHON_NODE_MODELS_MODULE, "SqlResourceRef")?, + host::entry(py, UserCodeHost::SqlResourceRef)?, ), ("explicit_kinds", explicit_reference_kinds(py)?.into_any()), ( "declaration_identity", - imported(py, SCOPE_MODELS_MODULE, "DeclarationIdentity")?, + registry::class(py, RecordId::DeclarationIdentity)?, ), ( "sql_hook_kind", - imported(py, SCOPE_TYPES_MODULE, "DeclarationKind")?.getattr("SQL_HOOK")?, + registry::class(py, RecordId::DeclarationKind)?.getattr("SQL_HOOK")?, ), ], ) @@ -191,7 +181,7 @@ pub(crate) fn build_model_inputs<'py>( let defer_model_sql_validation: bool = arguments.flag("defer_model_sql_validation")?; let external: Bound<'py, PyAny> = arguments.value("external_sql_reference_resolver")?; let settings: Bound<'py, PyAny> = context.getattr("effective_settings")?; - let validator: Bound<'py, PyAny> = imported(py, NATIVE_MODULE, "NativeModelValidator")? + let validator: Bound<'py, PyAny> = registry::native(py, NativeAttr::NativeModelValidator)? .call_method1( "from_inputs", ( @@ -213,7 +203,7 @@ pub(crate) fn build_model_inputs<'py>( }; let project_config_path: Bound<'py, PyAny> = root.div(PROJECT_CONFIG_FILENAME)?; let config_inputs: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "NativeModelConfigInputs")?, + ®istry::class(py, RecordId::NativeModelConfigInputs)?, &[ ( "project_config", @@ -243,15 +233,15 @@ pub(crate) fn build_model_inputs<'py>( vec![ ( "report_diagnostic", - imported(py, COLLECTOR_MODULE, "report_compile_diagnostic")?, + registry::native(py, NativeAttr::ReportCompileDiagnostic)?, ), ( "record_template_reads", - imported(py, NATIVE_MODULE, "record_template_reads")?, + registry::native(py, NativeAttr::RecordTemplateReads)?, ), ( "visible_declarations", - imported(py, NATIVE_RECORDS_MODULE, "VisibleModelDeclarations")?, + registry::class(py, RecordId::VisibleModelDeclarations)?, ), ("config_session", session), ("syntax_help", opt_out_help(py, true)?), @@ -279,7 +269,7 @@ pub(crate) fn build_model_inputs<'py>( )?; let syntax: Bound<'py, PyAny> = context.getattr("sql_lexical_syntax")?; let native_loop: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "NativeModelLoop")?.call1(( + registry::native(py, NativeAttr::NativeModelLoop)?.call1(( (validator, contract_classes(py)?.into_any(), classes), syntax.getattr("native_mapping")?, reserved_cursor_markers(), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/sql_expansion_context.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/sql_expansion_context.rs index bf28cee411..e2588c9eea 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/sql_expansion_context.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/sql_expansion_context.rs @@ -4,15 +4,11 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::pyfunction; use pyo3::types::{PyBool, PyDict, PyDictMethods}; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; - -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const USER_MACROS_MODULE: &str = "sqlbuild.compiler.compile._helpers.render.user_macros"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; /// Build the declaration scope of `discovered`; a static scope lends only its index to macros. fn declaration_scope<'py>( @@ -21,11 +17,11 @@ fn declaration_scope<'py>( sql_lexical_syntax: Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = discovered.py(); - let loaded_macros: Bound<'py, PyAny> = imported(py, USER_MACROS_MODULE, "load_project_macros")? + let loaded_macros: Bound<'py, PyAny> = host::entry(py, UserCodeHost::LoadProjectMacros)? .call1((discovered.getattr("macro_files")?,))?; if !static_scope.is_none() { let rebound: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "rebind_declaration_scope")?, + ®istry::native(py, NativeAttr::RebindDeclarationScope)?, &[ ("scope", static_scope), ("discovered_inputs", discovered.clone()), @@ -37,7 +33,7 @@ fn declaration_scope<'py>( } } call_with( - &imported(py, NATIVE_MODULE, "build_declaration_scope")?, + ®istry::native(py, NativeAttr::BuildDeclarationScope)?, &[ ("discovered_inputs", discovered.clone()), ("loaded_macros", loaded_macros), @@ -49,8 +45,7 @@ fn declaration_scope<'py>( /// Each model file's own enum and constant declarations, by file path. fn local_declarations<'py>(discovered: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = discovered.py(); - let context: Bound<'py, PyAny> = - imported(py, COMPILE_MODELS_MODULE, "DeclarationResolutionContext")?; + let context: Bound<'py, PyAny> = registry::class(py, RecordId::DeclarationResolutionContext)?; let by_name = |declarations: Bound<'py, PyAny>| -> PyResult> { let named: Bound<'py, PyDict> = PyDict::new(py); for declaration in declarations.try_iter()? { @@ -104,7 +99,7 @@ pub(crate) fn build_sql_expansion_context<'py>( let mut discovered: Bound<'py, PyAny> = arguments.value("discovered_inputs")?; if discovered.is_none() { discovered = call_with( - &imported(py, NATIVE_MODULE, "discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[ ("project_dir", arguments.value("project_dir")?), ( @@ -121,7 +116,7 @@ pub(crate) fn build_sql_expansion_context<'py>( cli_vars = PyDict::new(py).into_any(); } let effective_vars: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "build_effective_vars")?, + ®istry::native(py, NativeAttr::BuildEffectiveVars)?, &[ ("project_config", project_config.clone()), ("local_config", local_config.clone()), @@ -138,23 +133,23 @@ pub(crate) fn build_sql_expansion_context<'py>( )?; } let (enums, constants): (Bound<'py, PyAny>, Bound<'py, PyAny>) = - imported(py, NATIVE_MODULE, "public_declaration_indexes")? + registry::native(py, NativeAttr::PublicDeclarationIndexes)? .call1((&discovered,))? .extract()?; let collection_rendering: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "resolve_effective_collection_rendering")?, + ®istry::native(py, NativeAttr::ResolveEffectiveCollectionRendering)?, &[ ("project_config", project_config.clone()), ("declaration_override", py.None().into_bound(py)), ], )?; let macro_context: Bound<'py, PyAny> = call_with( - &imported(py, COMPILE_MODELS_MODULE, "MacroContext")?, + ®istry::class(py, RecordId::MacroContext)?, &[ ( "adapter_name", call_with( - &imported(py, NATIVE_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", project_config.clone()), ("local_config", local_config), @@ -178,7 +173,7 @@ pub(crate) fn build_sql_expansion_context<'py>( ], )?; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "SqlExpansionContext")?, + ®istry::class(py, RecordId::SqlExpansionContext)?, &[ ("effective_vars", effective_vars), ("loaded_macros", scope.getattr("loaded_macros")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/storage_policies.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/storage_policies.rs index 9d91bb850e..321a245879 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/storage_policies.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/storage_policies.rs @@ -3,18 +3,17 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyBool, PyString, PyTuple}; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; - -const CONTRACT_MODELS_MODULE: &str = "sqlbuild._native"; -const CONTRACT_TYPES_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; /// `value`, unless it is `None`. fn present(value: Bound<'_, PyAny>) -> Option> { Some(value).filter(|value| !value.is_none()) } -fn source<'py>(py: Python<'py>, class: &str, member: &str) -> PyResult> { - imported(py, CONTRACT_TYPES_MODULE, class)?.getattr(member) +fn source<'py>(py: Python<'py>, class: RecordId, member: &str) -> PyResult> { + registry::member(py, class, member) } /// The `materialized` defaults entry, or `None` for a model without a table-backed materialization. @@ -35,9 +34,8 @@ fn retention<'py>( target: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = materialized.py(); - let resolved: Bound<'py, PyAny> = - imported(py, CONTRACT_MODELS_MODULE, "ResolvedTimeTravelRetention")?; - let class = "TimeTravelRetentionSource"; + let resolved: Bound<'py, PyAny> = registry::class(py, RecordId::ResolvedTimeTravelRetention)?; + let class = RecordId::TimeTravelRetentionSource; let mut policy: Option<(Bound<'py, PyAny>, &str)> = None; if let Some(entry) = entry { if !target.is_none() { @@ -74,8 +72,8 @@ fn table_type<'py>( target: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = target.py(); - let resolved: Bound<'py, PyAny> = imported(py, CONTRACT_MODELS_MODULE, "ResolvedTableType")?; - let class = "TableTypeSource"; + let resolved: Bound<'py, PyAny> = registry::class(py, RecordId::ResolvedTableType)?; + let class = RecordId::TableTypeSource; let mut declared: Option<(Bound<'py, PyAny>, &str)> = None; if let Some(entry) = entry { if !target.is_none() { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/target.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/target.rs index 54674bfb26..3100046009 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/target.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/target.rs @@ -16,9 +16,9 @@ use crate::bindings::_helpers::frontier::constants::{ COMPILER_CACHE_DIRECTORY_NAME, RETIRED_CACHE_NAMESPACE_SUFFIXES, }; use crate::bindings::_helpers::model_config::template_data::{early_model_templates, run_id}; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; -const TARGET_NAME_MODULE: &str = "sqlbuild._native"; -const TARGET_CONFIG_MODULE: &str = "sqlbuild._native"; const DATABASE_KEY: &str = "database"; const SCHEMA_KEY: &str = "schema"; @@ -31,7 +31,7 @@ fn resolved_target<'py>( let project_config: Bound<'py, PyAny> = discovered.getattr("project_config")?; let local_config: Bound<'py, PyAny> = discovered.getattr("local_config")?; let target_name: Bound<'py, PyAny> = call_with( - &imported(py, TARGET_NAME_MODULE, "resolve_target_name")?, + ®istry::native(py, NativeAttr::ResolveTargetName)?, &[ ("project_config", project_config.clone()), ("local_config", local_config.clone()), @@ -42,7 +42,7 @@ fn resolved_target<'py>( return Ok((target_name, py.None().into_bound(py))); } let target_config: Bound<'py, PyAny> = call_with( - &imported(py, TARGET_CONFIG_MODULE, "resolve_target_config")?, + ®istry::native(py, NativeAttr::ResolveTargetConfig)?, &[ ("project_config", project_config), ("local_config", local_config), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_command/prepared_artifacts.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_command/prepared_artifacts.rs index a5141150a8..6b80fbf6ae 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_command/prepared_artifacts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_command/prepared_artifacts.rs @@ -15,6 +15,8 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, keywords, }; use crate::bindings::_helpers::compile_outputs::artifacts::staged_artifact_files; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; const MIN_PREPARED_ARTIFACT_MODELS: usize = 128; const MAX_PREPARED_ARTIFACT_MODELS: usize = 5000; @@ -286,7 +288,7 @@ impl PreparedCompileArtifacts { -> PyResult> { let py: Python<'_> = arguments.py(); Ok(call_with( - &imported(py, "sqlbuild._native", "plan_static_sql_tests")?, + ®istry::native(py, NativeAttr::PlanStaticSqlTests)?, &[ ("target_dir", planned_target.bind(py).clone()), ("adapter", planned_adapter.bind(py).clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_command/report.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_command/report.rs index eeb8de479d..dab6864f69 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_command/report.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_command/report.rs @@ -20,12 +20,12 @@ use crate::bindings::_helpers::compile_outputs::json_report::{ }; use crate::bindings::_helpers::discovery::constants::SQL_HOOK_OUTPUT_FIELDS; use crate::bindings::_helpers::discovery::hook_entries::hook_entries; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::semantic_shapes::coverage::semantic_coverage; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const HOOK_IDENTITIES_MODULE: &str = "sqlbuild.compiler.python_nodes.main._hook_identities"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const METADATA_MODULE: &str = "importlib.metadata"; const TARGET_DIRECTORY: &str = "target"; const COMPILED_DIRECTORY: &str = "compiled"; @@ -661,7 +661,7 @@ fn hook_definitions<'py>(project: &Bound<'py, PyAny>) -> PyResult = project.getattr("hook_functions")?; let identities: Bound<'py, PyAny> = - imported(py, HOOK_IDENTITIES_MODULE, "build_hook_identities")?.call1((&functions,))?; + host::entry(py, UserCodeHost::BuildHookIdentities)?.call1((&functions,))?; for hook in functions.try_iter()? { let hook: Bound<'py, PyAny> = hook?; let identity: Bound<'py, PyAny> = identities.get_item(hook.getattr("name")?)?; @@ -761,21 +761,18 @@ fn audit_resource<'py>(audit: &Bound<'py, PyAny>) -> PyResult fn test_resource<'py>(test: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = test.py(); let payload: Bound<'py, PyAny> = test.getattr("payload")?; - let expected_models: Bound<'py, PyList> = if payload.is_instance(&imported( - py, - NATIVE_RECORDS_MODULE, - "CompiledModelSqlTestPayload", - )?)? { - PyList::new( - py, - payload - .getattr("expected_model_names")? - .try_iter()? - .collect::>>()?, - )? - } else { - PyList::empty(py) - }; + let expected_models: Bound<'py, PyList> = + if payload.is_instance(®istry::class(py, RecordId::CompiledModelSqlTestPayload)?)? { + PyList::new( + py, + payload + .getattr("expected_model_names")? + .try_iter()? + .collect::>>()?, + )? + } else { + PyList::empty(py) + }; let test_file_path: Bound<'py, PyAny> = test.getattr("test_file")?.getattr("relative_path")?; let item: Bound<'py, PyDict> = dict( py, @@ -1042,7 +1039,7 @@ pub(crate) fn format_compile_error_json<'py>( ) }; let diagnostic: Bound<'py, PyAny> = call_with( - &imported(py, COMPILE_MODELS_MODULE, "CompilerDiagnostic")?, + ®istry::class(py, RecordId::CompilerDiagnostic)?, &[ ("phase", any(py, "compile")?), ("severity", any(py, ERROR_SEVERITY)?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_command/reuse_attempt.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_command/reuse_attempt.rs index dfaef4e9b9..134f294821 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_command/reuse_attempt.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_command/reuse_attempt.rs @@ -34,12 +34,14 @@ use crate::bindings::_helpers::compile_driver::phases::{elapsed_ms, monotonic}; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; use crate::bindings::_helpers::compile_outputs::json_report::plain_json; use crate::bindings::_helpers::frontier::cache_directory::cache_directory; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::facade::FacadeHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const REUSE_REQUEST: (&str, &str) = ("sqlbuild.cli.compile_reuse.models", "CompileReuseRequest"); -const STARTUP_INPUTS_MODULE: &str = "sqlbuild.cli.compile_reuse._helpers.startup_inputs"; -const COMPUTE_LOG_TEE_MODULE: &str = "sqlbuild.runtime.compute_logs.classes.text_tee"; +const REUSE_REQUEST: FacadeHost = FacadeHost::CompileReuseRequest; const HIT: &str = "hit"; const CHECKED_HIT: &str = "hit"; const CHECKED_BYPASS: &str = "bypass"; @@ -60,10 +62,9 @@ pub(crate) fn reuse_rules(py: Python<'_>, with_output_files: bool) -> PyResult, with_output_files: bool) -> PyResult( let capture: Bound<'py, PyAny> = if py .import("sys")? .getattr("stdout")? - .is_instance(&imported(py, COMPUTE_LOG_TEE_MODULE, "TextComputeLogTee")?)? + .is_instance(&host::entry(py, ExecutorHost::TextComputeLogTee)?)? { let logs: Bound<'py, PyAny> = resolved .call_method1("__truediv__", (COMPUTE_LOG_DIRECTORY_NAME,))? @@ -181,7 +182,7 @@ fn launch_payload<'py>( payload.set_item("search_path", search_path)?; payload.set_item( "startup_inputs", - imported(py, STARTUP_INPUTS_MODULE, "startup_inputs")?.call0()?, + host::entry(py, FacadeHost::StartupInputs)?.call0()?, )?; payload.set_item("json_output", attempt.getattr("json_output")?)?; payload.set_item("rules", reuse_rules(py, false)?)?; @@ -380,7 +381,7 @@ fn attempt_compile_reuse<'py>(request: &Bound<'py, PyAny>) -> PyResult = imported(py, NATIVE_MODULE, "check_compile_reuse")? + let native: Bound<'py, PyAny> = registry::native(py, NativeAttr::CheckCompileReuse)? .call1((reuse_check(request, &project_dir)?,))?; let checked: String = native.call_method0("outcome")?.extract()?; let json_output: Bound<'py, PyAny> = request.getattr("json_output")?; @@ -467,7 +468,7 @@ fn reuse_request<'py>(request: &Bound<'py, PyAny>) -> PyResult fields.push((name, request.getattr(name)?)); } fields.push(("profiling", profiling.into_bound_py_any(py)?)); - call_with(&imported(py, REUSE_REQUEST.0, REUSE_REQUEST.1)?, &fields) + call_with(&host::entry(py, REUSE_REQUEST)?, &fields) } /// Run one compile command request, replaying the previous compile when nothing changed. @@ -479,7 +480,7 @@ fn run_compile<'py>(request: &Bound<'py, PyAny>) -> PyResult> if attempt.getattr("outcome")?.eq(outcome(py, HIT)?)? && !exit_code.is_none() { return Ok(exit_code); } - imported(py, NATIVE_MODULE, "run_compile_command")?.call1((request, attempt)) + registry::native(py, NativeAttr::RunCompileCommand)?.call1((request, attempt)) } pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_command/reuse_recording.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_command/reuse_recording.rs index c0f5edb951..558889c3e9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_command/reuse_recording.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_command/reuse_recording.rs @@ -18,10 +18,10 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, keywords, }; use crate::bindings::_helpers::compile_outputs::reuse::artifact_digest; +use crate::bindings::_helpers::native_host::facade::FacadeHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; -const PROVIDER_SETTINGS_MODULE: &str = "sqlbuild.cli.compile_reuse._helpers.provider_settings"; -const MACRO_STORE_PATHS_MODULE: &str = - "sqlbuild.compiler.macro_bridge.main._macro_store_module_paths"; const MISS_OUTCOME: &str = "miss"; const HIT_OUTCOME: &str = "hit"; const BYPASS_OUTCOME: &str = "bypass"; @@ -292,12 +292,10 @@ fn module_paths(py: Python<'_>) -> PyResult> { paths.append(path)?; } } - let extra: Bound<'_, PyAny> = py.import("builtins")?.getattr("sorted")?.call1((imported( - py, - MACRO_STORE_PATHS_MODULE, - "macro_store_module_paths", - )? - .call0()?,))?; + let extra: Bound<'_, PyAny> = py + .import("builtins")? + .getattr("sorted")? + .call1((host::entry(py, UserCodeHost::MacroStoreModulePaths)?.call0()?,))?; for path in extra.try_iter()? { let path: Bound<'_, PyAny> = path?; if path.is_instance_of::() { @@ -351,7 +349,7 @@ struct CompileFacts<'py> { fn record<'py>(facts: &CompileFacts<'py>) -> PyResult { let py: Python<'py> = facts.native.py(); let settings: Bound<'_, PyAny> = call_with( - &imported(py, PROVIDER_SETTINGS_MODULE, "provider_settings_inputs")?, + &host::entry(py, FacadeHost::ProviderSettingsInputs)?, &[( "settings_classes", facts.input_reads.getattr("settings_classes")?, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_command/runtime_identity.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_command/runtime_identity.rs index 9e4f5df9cf..4a0af16010 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_command/runtime_identity.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_command/runtime_identity.rs @@ -6,8 +6,9 @@ use pyo3::{IntoPyObjectExt, pyfunction, wrap_pyfunction}; use sqlbuild_cli::replay::main::output_file_identities::output_file_identities; use crate::bindings::_helpers::compile_driver::python_calls::keywords; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; /// The version of the runtime identity a stored compile records. const REUSE_FORMAT_VERSION: &str = "1"; /// Each `CompileReuseRequest` field the invocation identity records unchanged. @@ -54,7 +55,7 @@ pub(crate) fn runtime_identity(py: Python<'_>) -> PyResult> { identity.set_item("cache_tag", cache_tag)?; identity.set_item( "native_build", - py.import(NATIVE_MODULE)?.getattr("BUILD_IDENTITY")?.str()?, + registry::native(py, NativeAttr::BuildIdentity)?.str()?, )?; Ok(identity) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_command/target_writer.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_command/target_writer.rs index c0ede29fd4..60946e133f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_command/target_writer.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_command/target_writer.rs @@ -32,6 +32,10 @@ use crate::bindings::_helpers::compile_outputs::artifacts::{ use crate::bindings::_helpers::compile_outputs::runtime_target::{ compiled_sql_test_path, planned_sql_test_path, }; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; const COMPILED_DIR: &str = "compiled"; const MODELS_DIR: &str = "models"; @@ -46,13 +50,6 @@ const SQL_FILE_SUFFIX: &str = ".sql"; const LANGUAGE_DIR_DEPTH: usize = 2; const PUBLISHED_STAGING_CHANGED: &str = "changed"; const PUBLISHED_TREE: &str = "tree"; -const OUTPUT_MODELS_MODULE: &str = "sqlbuild._native"; -const CLI_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; -const PLANNER_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; -const MODEL_CHAIN_MODULE: &str = "sqlbuild._native"; -const PLAN_ARTIFACTS_MODULE: &str = "sqlbuild._native"; -const LIFECYCLE_MODULE: &str = "sqlbuild.runtime.observability.classes.operation_lifecycle"; const TARGET_OPTIONS: (&[&str], &[&str]) = (&["target_dir", "adapter", "project"], &["manifest"]); const STAGING_CHANGED_HELP: &str = "Rerun the compile; avoid deleting target/ while a compile is running."; @@ -204,7 +201,7 @@ fn written_target<'py>( let py: Python<'py> = target_dir.py(); let [models, seeds, functions, audits, tests] = counts; call_with( - &imported(py, OUTPUT_MODELS_MODULE, "WrittenTarget")?, + ®istry::class(py, RecordId::WrittenTarget)?, &[ ("model_count", models.into_bound_py_any(py)?), ("seed_count", seeds.into_bound_py_any(py)?), @@ -349,7 +346,7 @@ fn planned_artifacts<'py>( return Ok(Vec::new()); } let lifecycle: Bound<'py, PyAny> = call_with( - &imported(py, LIFECYCLE_MODULE, "OperationLifecycle")?, + &host::entry(py, ExecutorHost::OperationLifecycle)?, &[ ("operation_kind", "project".into_bound_py_any(py)?), ("operation_name", "sql_test_planning".into_bound_py_any(py)?), @@ -357,11 +354,7 @@ fn planned_artifacts<'py>( )?; within(&lifecycle, |entered| { let artifacts: Vec> = call_with( - &imported( - py, - PLAN_ARTIFACTS_MODULE, - "plan_compiled_sql_test_artifacts", - )?, + ®istry::native(py, NativeAttr::PlanCompiledSqlTestArtifacts)?, &[ ("project", project.clone()), ("tests", PyTuple::new(py, tests)?.into_any()), @@ -402,7 +395,7 @@ fn plan_tests<'py>( } else { let context: IdentityContext<'py> = identity_context(project, adapter)?; let chains: Bound<'py, PyAny> = call_with( - &imported(py, MODEL_CHAIN_MODULE, "sql_test_model_chain_names_by_key")?, + ®istry::native(py, NativeAttr::SqlTestModelChainNamesByKey)?, &[("project", project.clone()), ("tests", tests.clone())], )?; Some((context, chains)) @@ -483,14 +476,13 @@ fn artifact_diagnostics<'py>( let mut diagnostics: Vec> = Vec::new(); for message in artifact.getattr("error_messages")?.try_iter()? { diagnostics.push(call_with( - &imported(py, COMPILE_MODELS_MODULE, "CompilerDiagnostic")?, + ®istry::class(py, RecordId::CompilerDiagnostic)?, &[ ("phase", "test".into_bound_py_any(py)?), ("severity", "error".into_bound_py_any(py)?), ( "code", - imported(py, PLANNER_EXCEPTIONS_MODULE, "PlannerInputError")? - .getattr("code")?, + registry::error(py, ErrorId::PlannerInput)?.getattr("code")?, ), ("message", message?), ("resource_type", "sql_test".into_bound_py_any(py)?), @@ -693,14 +685,13 @@ pub(crate) fn publish_staged_target<'py>( /// `StagedArtifactsChangedError(message, help=...)`. pub(crate) fn staging_changed(py: Python<'_>, message: &str) -> PyResult { - let error: Bound<'_, PyAny> = - imported(py, CLI_EXCEPTIONS_MODULE, "StagedArtifactsChangedError")?.call( - (message,), - Some(&keywords( - py, - &[("help", STAGING_CHANGED_HELP.into_bound_py_any(py)?)], - )?), - )?; + let error: Bound<'_, PyAny> = registry::error(py, ErrorId::StagedArtifactsChanged)?.call( + (message,), + Some(&keywords( + py, + &[("help", STAGING_CHANGED_HELP.into_bound_py_any(py)?)], + )?), + )?; Ok(PyErr::from_value(error)) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_command/test_artifact_cache.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_command/test_artifact_cache.rs index a9c0a96441..045eab9cf4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_command/test_artifact_cache.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_command/test_artifact_cache.rs @@ -12,8 +12,8 @@ use sqlbuild_cache::digest::main::sha256_hex::sha256_hex; use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::compile_outputs::json_report::{compact_sorted_json, orjson_sorted}; - -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; const CACHE_VERSION: i64 = 4; const ALGORITHM_FINGERPRINT: &str = "sql-test-artifact-v4-orjson-identity"; @@ -216,11 +216,7 @@ fn test_payload<'py>(test: &Bound<'py, PyAny>) -> PyResult> { ], )?; let test_payload: Bound<'py, PyAny> = test.getattr("payload")?; - if test_payload.is_instance(&imported( - py, - NATIVE_RECORDS_MODULE, - "CompiledModelSqlTestPayload", - )?)? { + if test_payload.is_instance(®istry::class(py, RecordId::CompiledModelSqlTestPayload)?)? { let block: Bound<'py, PyAny> = test.getattr("test_block")?; payload.set_item( "model", @@ -255,10 +251,9 @@ fn test_payload<'py>(test: &Bound<'py, PyAny>) -> PyResult> { ], )?, )?; - } else if test_payload.is_instance(&imported( + } else if test_payload.is_instance(®istry::class( py, - NATIVE_RECORDS_MODULE, - "CompiledDirectLogicSqlTestPayload", + RecordId::CompiledDirectLogicSqlTestPayload, )?)? { payload.set_item( "direct", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/analysis.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/analysis.rs index 1700417c93..8c038f3b60 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/analysis.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/analysis.rs @@ -10,35 +10,26 @@ use crate::bindings::_helpers::command_runtime::adapters::resolve_cli_adapter; use crate::bindings::_helpers::compile_driver::python_calls::{ Status, call_with, imported, keywords, within, }; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::constants::RULE_FIX_AVAILABLE_NOTE; use crate::bindings::_helpers::rules::early_sql_lint::EarlySqlLint; use crate::bindings::_helpers::rules::results::rules_progress; use crate::bindings::_helpers::rules::rules_config::load_rules_config; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const DISCOVER_MODULE: &str = "sqlbuild._native"; -const REJECT_SELECTORS_MODULE: &str = "sqlbuild._native"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; -const LIFECYCLE_MODULE: &str = "sqlbuild.runtime.observability.classes.operation_lifecycle"; -const SELECTED_GRAPH_MODULE: &str = "sqlbuild._native"; const RICH_LINEAGE: &str = "rich"; const FAST_LINEAGE: &str = "fast"; const RICH_COLUMN_LINEAGE: &str = "RICH"; const FAST_COLUMN_LINEAGE: &str = "FAST"; -const CONTRACTS_MODULE: &str = "sqlbuild._native"; -const CONTRACT_MODELS_MODULE: &str = "sqlbuild._native"; -const SELECTION_MODULE: &str = "sqlbuild._native"; -const RULE_THREADS_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; -const CLI_COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const GENERIC_DIALECT: &str = "generic"; const TARGET_DIRECTORY: &str = "target"; /// `ColumnLineageMode.`. fn column_lineage_mode<'py>(py: Python<'py>, member: &str) -> PyResult> { - imported(py, NATIVE_RECORDS_MODULE, "ColumnLineageMode")?.getattr(member) + registry::class(py, RecordId::ColumnLineageMode)?.getattr(member) } /// Column lineage in the compile's mode; `none` skips the analyzer. @@ -55,7 +46,7 @@ fn compile_lineage<'py>( } else { return Ok(py.None().into_bound(py)); }; - imported(py, "sqlbuild._native", "build_project_column_lineage")?.call1(( + registry::native(py, NativeAttr::BuildProjectColumnLineage)?.call1(( graph.getattr("project")?, dialect, column_lineage_mode(py, member)?, @@ -194,7 +185,7 @@ fn analyzed<'py>( let (lineage, lineage_ms): (Bound<'py, PyAny>, u64) = driver.lineage(&graph, &dialect)?; let contracts: ContractPhase<'py> = driver.contracts(&project, &dialect)?; let selected_keys: Bound<'py, PyAny> = call_with( - &imported(py, SELECTION_MODULE, "select_project_graph")?, + ®istry::native(py, NativeAttr::SelectProjectGraph)?, &[ ("graph", graph.clone()), ("select", driver.select.clone()), @@ -223,7 +214,7 @@ fn analyzed<'py>( let diagnostics: Bound<'py, PyTuple> = with_rule_diagnostics(py, &core_diagnostics, &rules_result)?; call_with( - &imported(py, CLI_COMPILE_MODELS_MODULE, "CompileAnalysis")?, + ®istry::class(py, RecordId::CompileAnalysis)?, &[ ("discovered_inputs", discovered), ("adapter", adapter), @@ -263,7 +254,7 @@ fn analyzed<'py>( fn empty_rules_result(py: Python<'_>) -> PyResult> { let zero: Bound<'_, PyAny> = 0_i64.into_pyobject(py)?.into_any(); call_with( - &imported(py, NATIVE_RECORDS_MODULE, "RulesRunResult")?, + ®istry::class(py, RecordId::RulesRunResult)?, &[ ("findings", PyTuple::empty(py).into_any()), ("evaluated_models", zero.clone()), @@ -291,7 +282,7 @@ impl<'py> Driver<'_, 'py> { py.None().into_bound(py) }; let discovered: Bound<'py, PyAny> = call_with( - &imported(py, DISCOVER_MODULE, "discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[ ("project_dir", self.options.project_dir.clone()), ("extract_output_column_locations", false_value), @@ -299,7 +290,7 @@ impl<'py> Driver<'_, 'py> { ], )?; call_with( - &imported(py, REJECT_SELECTORS_MODULE, "reject_unit_test_selectors")?, + ®istry::native(py, NativeAttr::RejectUnitTestSelectors)?, &[ ("select", self.select.clone()), ("exclude", self.exclude.clone()), @@ -315,11 +306,7 @@ impl<'py> Driver<'_, 'py> { /// The adapter the project and local configuration select. fn adapter(&self, discovered: &Bound<'py, PyAny>) -> PyResult> { let adapter_name: Bound<'py, PyAny> = call_with( - &imported( - self.py, - ADAPTER_NAME_MODULE, - "resolve_effective_adapter_name", - )?, + ®istry::native(self.py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), @@ -339,7 +326,7 @@ impl<'py> Driver<'_, 'py> { let started: Instant = Instant::now(); self.status.start("Compiling project graph...")?; let lifecycle: Bound<'py, PyAny> = call_with( - &imported(py, LIFECYCLE_MODULE, "OperationLifecycle")?, + &host::entry(py, ExecutorHost::OperationLifecycle)?, &[ ("operation_kind", PyString::new(py, "project").into_any()), ( @@ -366,11 +353,7 @@ impl<'py> Driver<'_, 'py> { )?; let graph: Bound<'py, PyAny> = within(&lifecycle, |_| { call_with( - &imported( - py, - SELECTED_GRAPH_MODULE, - "build_project_graph_with_analysis_selection", - )?, + ®istry::native(py, NativeAttr::BuildProjectGraphWithAnalysisSelection)?, &[ ("discovered_inputs", discovered.clone()), ("adapter", adapter.clone()), @@ -427,13 +410,13 @@ impl<'py> Driver<'_, 'py> { .is_truthy()?; let result: Bound<'py, PyAny> = if skip { call_with( - &imported(py, CONTRACT_MODELS_MODULE, "ContractValidationResult")?, + ®istry::class(py, RecordId::ContractValidationResult)?, &[("diagnostics", PyTuple::empty(py).into_any())], )? } else { self.status.start("Validating model contracts...")?; call_with( - &imported(py, CONTRACTS_MODULE, "evaluate_model_contracts")?, + ®istry::native(py, NativeAttr::EvaluateModelContracts)?, &[("project", project.clone()), ("dialect", dialect.clone())], )? }; @@ -492,7 +475,7 @@ impl<'py> Driver<'_, 'py> { py.None().into_bound(py) }; let result: Bound<'py, PyAny> = call_with( - &imported(py, RULE_THREADS_MODULE, "evaluate_rules")?, + ®istry::native(py, NativeAttr::EvaluateRules)?, &[ ("graph", graph.clone()), ("discovered_inputs", discovered.clone()), @@ -521,12 +504,11 @@ fn with_rule_diagnostics<'py>( core: &Bound<'py, PyTuple>, rules_result: &Bound<'py, PyAny>, ) -> PyResult> { - let diagnostic_class: Bound<'py, PyAny> = - imported(py, COMPILE_MODELS_MODULE, "CompilerDiagnostic")?; + let diagnostic_class: Bound<'py, PyAny> = registry::class(py, RecordId::CompilerDiagnostic)?; let phase: Bound<'py, PyAny> = - imported(py, NATIVE_RECORDS_MODULE, "DiagnosticPhase")?.getattr("RULE")?; + registry::class(py, RecordId::DiagnosticPhase)?.getattr("RULE")?; let severity: Bound<'py, PyAny> = - imported(py, NATIVE_RECORDS_MODULE, "DiagnosticSeverity")?.getattr("ERROR")?; + registry::class(py, RecordId::DiagnosticSeverity)?.getattr("ERROR")?; let fix_note: Bound<'py, PyAny> = PyString::new(py, RULE_FIX_AVAILABLE_NOTE).into_any(); let mut diagnostics: Vec> = core.iter().collect(); for fault in rules_result.getattr("findings")?.try_iter()? { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/analysis_cache.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/analysis_cache.rs index 051a46aec1..410eb0dec9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/analysis_cache.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/analysis_cache.rs @@ -13,8 +13,8 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, }; use crate::bindings::_helpers::planner_identity::json_values::{COMPACT, unicode_sorted_dumps}; - -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; const ANALYSIS_CACHE_VERSION: i64 = 13; const ANALYSIS_ALGORITHM_FINGERPRINT: &str = "model-sql-analysis-v21-completed-star-shapes"; @@ -74,7 +74,7 @@ pub(crate) fn analysis_cache_context<'py>( Err(error) => return Err(error), }; let context = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "AnalysisCacheContext")?, + ®istry::class(py, RecordId::AnalysisCacheContext)?, &[ ("root", root.clone()), ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly.rs index e21a80bc05..a25a389139 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly.rs @@ -7,15 +7,12 @@ use pyo3::{pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::compile_driver::assembly_project::assembled_project; use crate::bindings::_helpers::compile_driver::project::lineage_mode; use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, within, + KeywordArguments, call_with, within, }; - -const COLLECTOR_MODULE: &str = "sqlbuild._native"; -const PROJECT_WITH_MODULE: &str = "sqlbuild._native"; -const RECORD_ANALYSES_MODULE: &str = "sqlbuild._native"; -const EXPLICIT_REFERENCES_MODULE: &str = "sqlbuild._native"; -const ACTIVE_BRIDGE_MODULE: &str = "sqlbuild.compiler.macro_bridge.main.active_macro_bridge"; -const RUN_BRIDGE_MODULE: &str = "sqlbuild.compiler.macro_bridge.classes.macro_bridge"; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; const OPTIONAL: [&str; 5] = [ "inference_profile", @@ -53,7 +50,7 @@ fn assemble_project<'py>( analysis_model_names: arguments.value("analysis_model_names")?.unbind(), }; let collector: Bound<'py, PyAny> = - imported(py, COLLECTOR_MODULE, "collect_compile_diagnostics")?.call0()?; + registry::native(py, NativeAttr::CollectCompileDiagnostics)?.call0()?; let (built, violations): (Bound<'py, PyAny>, Bound<'py, PyAny>) = within(&collector, |violations| { Ok((referenced_project(py, request)?, violations.clone())) @@ -64,7 +61,7 @@ fn assemble_project<'py>( } let diagnostics: Bound<'py, PyTuple> = chained(&built.getattr("diagnostics")?, &collected)?; call_with( - &imported(py, PROJECT_WITH_MODULE, "project_with")?, + ®istry::native(py, NativeAttr::ProjectWith)?, &[("project", built), ("diagnostics", diagnostics.into_any())], ) } @@ -86,7 +83,7 @@ pub(crate) fn chained<'py>( /// The undecorated `assemble_project`. fn referenced_project(py: Python<'_>, request: AssemblyRequest) -> PyResult> { let inputs: Bound<'_, PyAny> = request.inputs.bind(py).clone(); - let project: Bound<'_, PyAny> = if imported(py, ACTIVE_BRIDGE_MODULE, "active_macro_bridge")? + let project: Bound<'_, PyAny> = if host::entry(py, UserCodeHost::ActiveMacroBridge)? .call0()? .is_none() { @@ -99,7 +96,7 @@ fn referenced_project(py: Python<'_>, request: AssemblyRequest) -> PyResult, request: AssemblyRequest) -> PyResult = with_native_project(&project, &inputs.getattr("native_project")?)?; let references: Bound<'_, PyAny> = call_with( - &imported(py, EXPLICIT_REFERENCES_MODULE, "explicit_reference_report")?, + ®istry::native(py, NativeAttr::ExplicitReferenceReport)?, &[ ("project", project.clone()), ("discovered_inputs", inputs.getattr("discovered_inputs")?), @@ -129,7 +126,7 @@ fn referenced_project(py: Python<'_>, request: AssemblyRequest) -> PyResult = chained(&project.getattr("diagnostics")?, &reference_diagnostics)?; call_with( - &imported(py, PROJECT_WITH_MODULE, "project_with")?, + ®istry::native(py, NativeAttr::ProjectWith)?, &[ ("project", project), ("diagnostics", diagnostics.into_any()), @@ -148,7 +145,7 @@ fn with_native_project<'py>( return Ok(project.clone()); } call_with( - &imported(py, RECORD_ANALYSES_MODULE, "record_model_analyses")?, + ®istry::native(py, NativeAttr::RecordModelAnalyses)?, &[ ("project", native_project.clone()), ("models", project.getattr("models")?), @@ -158,7 +155,7 @@ fn with_native_project<'py>( return Ok(project.clone()); } call_with( - &imported(py, PROJECT_WITH_MODULE, "project_with")?, + ®istry::native(py, NativeAttr::ProjectWith)?, &[ ("project", project.clone()), ("native_project", native_project.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_analysis.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_analysis.rs index fbe498fcd0..6b3728c6e3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_analysis.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_analysis.rs @@ -12,14 +12,12 @@ use crate::bindings::_helpers::compile_driver::compile_timings::{ record_compile_cpu_timing, record_compile_timing, }; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ADAPTER_MODELS_MODULE: &str = "sqlbuild.adapter.contract.models"; -pub(crate) const ANALYSIS_MODELS_MODULE: &str = "sqlbuild._native"; const LINEAGE_TYPES_MODULE: &str = "sqlbuild.compiler.lineage.types"; -const PLANNER_TYPES_MODULE: &str = "sqlbuild._native"; const GENERIC_DIALECT: &str = "generic"; const UDF_PREFIX: &str = "__sqlbuild_udf_"; @@ -78,7 +76,7 @@ pub(crate) fn analysis_context<'py>( request.skip_column_inference.bind(py), )?; let relations: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "declared_relations")?.call1((inputs,))?; + registry::native(py, NativeAttr::DeclaredRelations)?.call1((inputs,))?; let column_types: Bound<'py, PyAny> = relations.get_item(0)?; let nullability: Bound<'py, PyAny> = relations.get_item(1)?; let complete_schemas: Bound<'py, PyAny> = relations.get_item(2)?; @@ -112,7 +110,7 @@ pub(crate) fn analysis_context<'py>( fn dynamic_families_by_table<'py>(inputs: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = inputs.py(); let enforced: Bound<'py, PyAny> = - imported(py, PLANNER_TYPES_MODULE, "ContractPolicy")?.getattr("ENFORCED")?; + registry::class(py, RecordId::ContractPolicy)?.getattr("ENFORCED")?; let families: Bound<'py, PyDict> = PyDict::new(py); for model_input in inputs.getattr("model_inputs")?.try_iter()? { let model_input: Bound<'py, PyAny> = model_input?; @@ -143,9 +141,9 @@ pub(crate) fn declared_names<'py>( let py: Python<'py> = inputs.py(); let functions: Bound<'py, PyAny> = inputs.getattr("sql_function_inputs")?; let names: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "known_function_names")?.call1((functions.clone(),))?; + registry::native(py, NativeAttr::KnownFunctionNames)?.call1((functions.clone(),))?; let types: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "known_declared_types")?, + ®istry::native(py, NativeAttr::KnownDeclaredTypes)?, &[ ("functions", functions), ("column_types", column_types.clone()), @@ -165,7 +163,7 @@ fn profile_with_catalog<'py>( let profile: Bound<'py, PyAny> = if inference_profile.is_truthy()? { inference_profile.clone() } else { - imported(py, ADAPTER_MODELS_MODULE, "ExpressionInferenceProfile")?.call0()? + host::entry(py, AdapterHost::ExpressionInferenceProfile)?.call0()? }; let (known_functions, known_types): (Bound<'py, PyAny>, Bound<'py, PyAny>) = declared_names(inputs, column_types)?; @@ -273,7 +271,7 @@ fn record_expression_shapes<'py>( } } let shapes: Vec> = call_with( - &imported(py, NATIVE_MODULE, "infer_native_expression_source_shapes")?, + ®istry::native(py, NativeAttr::InferNativeExpressionSourceShapes)?, &[ ("expressions", PyTuple::new(py, expressions)?.into_any()), ("profile", context.profile.clone()), @@ -336,7 +334,7 @@ fn model_analyses<'py>( let inputs: &Bound<'py, PyAny> = request.inputs.bind(py); let names: &Bound<'py, PyAny> = request.analysis_model_names.bind(py); let mode: &Bound<'py, PyAny> = request.column_lineage_mode.bind(py); - let lineage_mode: Bound<'py, PyAny> = imported(py, NATIVE_RECORDS_MODULE, "ColumnLineageMode")?; + let lineage_mode: Bound<'py, PyAny> = registry::class(py, RecordId::ColumnLineageMode)?; let rich: bool = mode.eq(lineage_mode.getattr("RICH")?)?; let allow_compact: bool = mode.eq(lineage_mode.getattr("FAST")?)? || rich; let flags: (Bound<'py, PyAny>, Bound<'py, PyAny>) = ( @@ -362,7 +360,7 @@ fn model_analyses<'py>( } } let analysis_request: Bound<'py, PyAny> = call_with( - &imported(py, ANALYSIS_MODELS_MODULE, "NativeModelAnalysisRequest")?, + ®istry::class(py, RecordId::NativeModelAnalysisRequest)?, &[ ("known_functions", known_functions), ("known_types", known_types), @@ -381,7 +379,7 @@ fn model_analyses<'py>( ], )?; call_with( - &imported(py, NATIVE_MODULE, "analyze_native_model_sql")?, + ®istry::native(py, NativeAttr::AnalyzeNativeModelSql)?, &[("request", analysis_request)], ) }) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_checks.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_checks.rs index 7f9878328f..05c6331f5d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_checks.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_checks.rs @@ -4,15 +4,12 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyBool, PyDict, PyDictMethods, PyTuple}; use crate::bindings::_helpers::attachments::cursor_intrinsics::validated_model_cursor_sql; -use crate::bindings::_helpers::compile_driver::assembly_project::ADAPTER_NAME_MODULE; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::render::declaration_values::declaration_scope_resolver; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; -const NATIVE_MODULE: &str = "sqlbuild._native"; /// The `CompiledSqlScenario` fields copied from its input, in Python's keyword order. const SCENARIO_FIELDS: [&str; 12] = [ "sql_body", @@ -37,10 +34,9 @@ pub(crate) fn test_model_query_overrides<'py>( let py: Python<'py> = inputs.py(); let overrides: Bound<'py, PyDict> = PyDict::new(py); let payload: Bound<'py, PyAny> = test_input.getattr("payload")?; - if !payload.is_instance(&imported( + if !payload.is_instance(®istry::class( py, - NATIVE_RECORDS_MODULE, - "CompileModelSqlTestInputPayload", + RecordId::CompileModelSqlTestInputPayload, )?)? { return Ok(overrides); } @@ -70,7 +66,7 @@ pub(crate) fn test_model_query_overrides<'py>( &inputs.getattr("scope_index")?, (&loaded_macros, &lookup), )?; - let expand: Bound<'py, PyAny> = py.import(NATIVE_MODULE)?.getattr("expand_sql_macros")?; + let expand: Bound<'py, PyAny> = registry::native(py, NativeAttr::ExpandSqlMacros)?; for model_input in inputs.getattr("model_inputs")?.try_iter()? { let model_input: Bound<'py, PyAny> = model_input?; let file_path: Bound<'py, PyAny> = @@ -115,14 +111,14 @@ fn model_macro_context<'py>(inputs: &Bound<'py, PyAny>) -> PyResult = call_with( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", inputs.getattr("project_config")?), ("local_config", inputs.getattr("local_config")?), ], )?; call_with( - &imported(py, COMPILE_MODELS_MODULE, "MacroContext")?, + ®istry::class(py, RecordId::MacroContext)?, &[ ("adapter_name", adapter_name), ( @@ -142,9 +138,8 @@ pub(crate) fn compiled_sql_scenarios<'py>( inputs: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = inputs.py(); - let scenario_class: Bound<'py, PyAny> = - imported(py, COMPILE_MODELS_MODULE, "CompiledSqlScenario")?; - let key_class: Bound<'py, PyAny> = imported(py, COMPILE_MODELS_MODULE, "CompiledObjectKey")?; + let scenario_class: Bound<'py, PyAny> = registry::class(py, RecordId::CompiledSqlScenario)?; + let key_class: Bound<'py, PyAny> = registry::class(py, RecordId::CompiledObjectKey)?; let resource_type: Bound<'py, PyAny> = imported(py, COMPILE_TYPES_MODULE, "CompiledResourceType")?.getattr("SQL_SCENARIO")?; let mut scenarios: Vec> = Vec::new(); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_facts.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_facts.rs index 9a89d802c2..81d7e22eac 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_facts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_facts.rs @@ -5,11 +5,10 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyFrozenSet, PyListMethods, PyTuple}; use crate::bindings::_helpers::compile_driver::assembly_analysis::AnalysisContext; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::project_assembly::models::{NativeModelFacts, NativeProjectFacts}; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ANALYSIS_MODELS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; /// `NativeProjectFacts` of `inputs`: resources, syntax validity, deps and standalone pivot proofs. pub(crate) fn native_project_facts<'py>( @@ -33,7 +32,7 @@ pub(crate) fn native_project_facts<'py>( PyDict::new(py).into_any() }; let syntax_checks: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "model_syntax_checks")?, + ®istry::native(py, NativeAttr::ModelSyntaxChecks)?, &[ ("model_inputs", model_inputs.clone()), ("analysis_model_names", names.clone()), @@ -41,7 +40,7 @@ pub(crate) fn native_project_facts<'py>( ], )?; let resources: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "assemble_native_project_resources")?, + ®istry::native(py, NativeAttr::AssembleNativeProjectResources)?, &[ ("inputs", inputs.clone()), ("dialect", dialect.clone()), @@ -50,7 +49,7 @@ pub(crate) fn native_project_facts<'py>( )?; let proofs: Bound<'py, PyDict> = pivot_proofs( &call_with( - &imported(py, NATIVE_MODULE, "standalone_pivot_models")?, + ®istry::native(py, NativeAttr::StandalonePivotModels)?, &[ ("model_inputs", model_inputs.clone()), ("analysed", positions), @@ -98,7 +97,7 @@ fn pivot_proofs<'py>( ) -> PyResult> { let py: Python<'py> = pivots.py(); let tables: Bound<'py, PyAny> = call_with( - &imported(py, ANALYSIS_MODELS_MODULE, "NativePivotTables")?, + ®istry::class(py, RecordId::NativePivotTables)?, &[ ("dialect", context.profile.getattr("sql_analysis_dialect")?), ("column_types_by_table", context.column_types.clone()), @@ -114,7 +113,7 @@ fn pivot_proofs<'py>( ], )?; let proven: Vec> = call_with( - &imported(py, NATIVE_MODULE, "prove_native_dynamic_contracts")?, + ®istry::native(py, NativeAttr::ProveNativeDynamicContracts)?, &[ ("session", session.clone()), ("tables", tables), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_models.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_models.rs index 6069d8cbeb..7975846def 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_models.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_models.rs @@ -6,15 +6,14 @@ use pyo3::types::{PyDict, PyList, PyString, PyTuple}; use crate::bindings::_helpers::attachments::cursor_intrinsics::analysis_cursor_sql; use crate::bindings::_helpers::boundary::panics::NativeCompilerError; use crate::bindings::_helpers::compile_driver::assembly_analysis::{ - ANALYSIS_MODELS_MODULE, AnalysisContext, model_stem, selected_for_analysis, + AnalysisContext, model_stem, selected_for_analysis, }; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::hand_built::compiled_model::compiled_model as hand_built_model; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::render::macro_calls::top_level_macro_call_names; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; const NATIVE_FAILURE_PREFIX: &str = "NativeCompilerError: "; const HOOK_NAMES: [&str; 2] = ["pre_hooks", "post_hooks"]; @@ -41,12 +40,11 @@ pub(crate) fn compiled_models<'py>( (native_models, names): (&Bound<'py, PyAny>, &Bound<'py, PyAny>), ) -> PyResult> { let py: Python<'py> = inputs.py(); - let native: Bound<'py, PyAny> = py.import(NATIVE_MODULE)?.into_any(); let stages: ModelStages<'py> = ModelStages { - placeholders: native.getattr("model_placeholders")?, - validate_hooks: native.getattr("validate_hook_sql_syntax")?, - validate_sql: native.getattr("validate_sql_syntax")?, - native_input: native.getattr("NativeModelInput")?, + placeholders: registry::native(py, NativeAttr::ModelPlaceholders)?, + validate_hooks: registry::native(py, NativeAttr::ValidateHookSqlSyntax)?, + validate_sql: registry::native(py, NativeAttr::ValidateSqlSyntax)?, + native_input: registry::native(py, NativeAttr::NativeModelInput)?, }; let analysis_enabled: bool = context.sql_analysis_enabled.is_truthy()?; let mut models: Vec> = Vec::new(); @@ -173,7 +171,7 @@ fn compiled_model<'py>( ); } let analysis: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "HandBuiltAnalysis")?, + ®istry::class(py, RecordId::HandBuiltAnalysis)?, &[ ("session", session_analysis), ("fallback", fallback), @@ -199,7 +197,7 @@ fn fallback_columns<'py>( ) -> PyResult> { let py: Python<'py> = analysis_query_sql.py(); let query: Bound<'py, PyAny> = call_with( - &imported(py, ANALYSIS_MODELS_MODULE, "NativeColumnQuery")?, + ®istry::class(py, RecordId::NativeColumnQuery)?, &[ ("sql", analysis_query_sql.clone()), ("mode", PyString::new(py, "parse").into_any()), @@ -207,7 +205,7 @@ fn fallback_columns<'py>( ], )?; call_with( - &imported(py, NATIVE_MODULE, "infer_native_query_column_rows")?, + ®istry::native(py, NativeAttr::InferNativeQueryColumnRows)?, &[ ("queries", PyTuple::new(py, [query])?.into_any()), ("profile", context.profile.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_project.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_project.rs index e848b2a2cf..1a57642e0f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_project.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_project.rs @@ -15,16 +15,15 @@ use crate::bindings::_helpers::compile_driver::assembly_models::{ binding_rows, compiled_models, project_binding_diagnostics, }; use crate::bindings::_helpers::compile_driver::assembly_sql_tests::assembled_sql_tests; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; +use crate::bindings::_helpers::compile_driver::python_calls::{call_with, keywords}; use crate::bindings::_helpers::model_config::template_data::early_model_templates; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::scopes::scope_errors::report_scope_index_errors; use crate::bindings::_helpers::scopes::scope_validation::scope_index_with_compile_usages; -const NATIVE_MODULE: &str = "sqlbuild._native"; -pub(crate) const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; -const ADAPTER_TYPES_MODULE: &str = "sqlbuild.adapter.contract.types"; -const SCENARIO_CONFIG_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const SOURCE_SLOT: &str = "source"; const SEED_SLOT: &str = "seed"; const AUDIT_SLOT: &str = "audit"; @@ -96,7 +95,7 @@ pub(crate) fn assembled_project<'py>( let project: Bound<'py, PyAny> = project_view(inputs, &fields, &models)?; let binding_results: Bound<'py, PyDict> = binding_rows(&context.analyses)?; call_with( - &imported(py, NATIVE_MODULE, "complete_semantic_project")?, + ®istry::native(py, NativeAttr::CompleteSemanticProject)?, &[ ("project", project), ("profile", context.profile.clone()), @@ -166,13 +165,13 @@ fn connection_database_fallback<'py>( ) -> PyResult> { let py: Python<'py> = inputs.py(); let adapter_name: Bound<'py, PyAny> = call_with( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", inputs.getattr("project_config")?), ("local_config", inputs.getattr("local_config")?), ], )?; - if adapter_name.eq(imported(py, ADAPTER_TYPES_MODULE, "BuiltinAdapter")?.getattr("DUCKDB")?)? { + if adapter_name.eq(host::entry(py, AdapterHost::BuiltinAdapter)?.getattr("DUCKDB")?)? { return Ok(py.None().into_bound(py)); } str_or_none(&connection.call_method1("get", ("database",))?) @@ -227,11 +226,7 @@ fn project_fields<'py>( ( "scenario", call_with( - &imported( - py, - SCENARIO_CONFIG_MODULE, - "resolve_effective_scenario_config", - )?, + ®istry::native(py, NativeAttr::ResolveEffectiveScenarioConfig)?, &[ ("project_config", project_config.clone()), ("local_config", inputs.getattr("local_config")?), @@ -334,7 +329,7 @@ fn project_view<'py>( ) -> PyResult> { let py: Python<'py> = inputs.py(); let owner: Bound<'py, PyAny> = inputs.getattr("native_project")?; - let native_model: Bound<'py, PyAny> = imported(py, NATIVE_MODULE, "NativeCompiledModel")?; + let native_model: Bound<'py, PyAny> = registry::native(py, NativeAttr::NativeCompiledModel)?; let mut all_native: bool = true; for model in models.as_any().try_iter()? { if !model?.is_instance(&native_model)? { @@ -361,5 +356,5 @@ fn project_view<'py>( } let arguments: Bound<'py, PyDict> = fields.copy()?; arguments.set_item("sql_expansions", sql_expansions)?; - imported(py, COMPILE_MODELS_MODULE, "CompiledProject")?.call((), Some(&arguments)) + registry::class(py, RecordId::CompiledProject)?.call((), Some(&arguments)) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_sql_tests.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_sql_tests.rs index e085317b33..9e768fec7e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_sql_tests.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/assembly_sql_tests.rs @@ -6,12 +6,9 @@ use pyo3::types::{PyList, PyTuple}; use crate::bindings::_helpers::boundary::panics::NativeCompilerError; use crate::bindings::_helpers::compile_driver::assembly_checks::test_model_query_overrides; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; -const COLLECTOR_MODULE: &str = "sqlbuild._native"; const DECIMAL_MODULE: &str = "decimal"; const DATACLASSES_MODULE: &str = "dataclasses"; const SQL_TEST_INPUT_FAILURE: &str = "input"; @@ -23,7 +20,7 @@ pub(crate) fn assembled_sql_tests<'py>( ) -> PyResult> { let py: Python<'py> = inputs.py(); let assemblies: Vec> = call_with( - &imported(py, NATIVE_MODULE, "assemble_native_sql_tests")?, + ®istry::native(py, NativeAttr::AssembleNativeSqlTests)?, &[("inputs", inputs.clone())], )? .try_iter()? @@ -37,9 +34,9 @@ pub(crate) fn assembled_sql_tests<'py>( "native SQL test assembly returned a different number of tests", )); } - let report: Bound<'py, PyAny> = imported(py, COLLECTOR_MODULE, "report_compile_diagnostic")?; + let report: Bound<'py, PyAny> = registry::native(py, NativeAttr::ReportCompileDiagnostic)?; let mocked_input: Bound<'py, PyAny> = - imported(py, NATIVE_RECORDS_MODULE, "CompileModelSqlTestInputPayload")?; + registry::class(py, RecordId::CompileModelSqlTestInputPayload)?; let mut tests: Vec> = Vec::with_capacity(assemblies.len()); for (test_input, assembly) in test_inputs.iter().zip(assemblies) { let failure: Bound<'py, PyAny> = assembly.getattr("failure")?; @@ -86,7 +83,7 @@ fn assembly_error( }; if kind == Some(SQL_TEST_INPUT_FAILURE) { let error: Bound<'_, PyAny> = - imported(py, COMPILE_ERRORS_MODULE, "CompileInputError")?.call1((message,))?; + registry::error(py, ErrorId::CompileInput)?.call1((message,))?; return Ok(PyErr::from_value(error)); } Ok(NativeCompilerError::new_err(message)) @@ -99,11 +96,7 @@ fn with_model_queries<'py>( ) -> PyResult> { let py: Python<'py> = test.py(); let payload: Bound<'py, PyAny> = test.getattr("payload")?; - if !payload.is_instance(&imported( - py, - NATIVE_RECORDS_MODULE, - "CompiledModelSqlTestPayload", - )?)? { + if !payload.is_instance(®istry::class(py, RecordId::CompiledModelSqlTestPayload)?)? { return Ok(test.clone()); } let replace: Bound<'py, PyAny> = imported(py, DATACLASSES_MODULE, "replace")?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/command.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/command.rs index d7ba67d013..7e1e5c63e4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/command.rs @@ -25,12 +25,11 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ Status, call_with, imported, keywords, within, }; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::results::format_skipped_type_proof_rules; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - const BYPASS_OUTCOME: &str = "bypass"; -const NATIVE_MODULE: &str = "sqlbuild._native"; /// What one full compile reports for reuse recording. struct CommandOutcome<'py> { @@ -152,11 +151,11 @@ fn recorded_compile<'py>( let compiled: PyResult<(CommandOutcome<'py>, Bound<'py, PyAny>, Bound<'py, PyAny>)> = collect_compile_timings(|detailed_timings| { within( - &imported(py, NATIVE_MODULE, "record_compile_input_reads")?.call0()?, + ®istry::native(py, NativeAttr::RecordCompileInputReads)?.call0()?, |input_reads| { let artifact_writes: Py = new_artifact_recorder(py)?; let manager: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "PreparedCompileArtifacts")?, + ®istry::native(py, NativeAttr::PreparedCompileArtifacts)?, &[ ( "enabled", @@ -194,7 +193,7 @@ fn recorded_compile<'py>( close_status(status)?; let (outcome, input_reads, artifact_writes) = compiled?; let stored: PyResult> = call_with( - &imported(py, NATIVE_MODULE, "write_reusable_compile")?, + ®istry::native(py, NativeAttr::WriteReusableCompile)?, &[ ("attempt", attempt.clone()), ("output", output.clone()), @@ -331,7 +330,7 @@ fn analysis_options<'py>( ) -> PyResult> { let py: Python<'py> = request.py(); let selection: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "CompileAnalysisSelection")?, + ®istry::class(py, RecordId::CompileAnalysisSelection)?, &[ ("select", request.getattr("select")?), ("exclude", request.getattr("exclude")?), @@ -358,7 +357,7 @@ fn analysis_options<'py>( fn rules_failed(analysis: &Bound<'_, PyAny>) -> PyResult { let py: Python<'_> = analysis.py(); let rule_phase: Bound<'_, PyAny> = - imported(py, NATIVE_RECORDS_MODULE, "DiagnosticPhase")?.getattr("RULE")?; + registry::class(py, RecordId::DiagnosticPhase)?.getattr("RULE")?; for diagnostic in analysis.getattr("diagnostics")?.try_iter()? { let diagnostic: Bound<'_, PyAny> = diagnostic?; if diagnostic.getattr("is_error")?.is_truthy()? diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/config_validation.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/config_validation.rs index 35a99e92ff..4eba2d7bfc 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/config_validation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/config_validation.rs @@ -4,14 +4,12 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python}; use pyo3::types::{PyBool, PyDict, PyString, PyTuple}; use sqlbuild_config::project::main::unsupported_target_warehouse::unsupported_target_warehouse; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const DISCOVERY_ERRORS_MODULE: &str = "sqlbuild._native"; -const SPEC_ERRORS_MODULE: &str = "sqlbuild._native"; -const TARGET_CONFIG_MODULE: &str = "sqlbuild._native"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; -const SESSION_WAREHOUSE_MODULE: &str = "sqlbuild.adapter.discovery.main._session_warehouse_support"; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr}; +use crate::bindings::_helpers::records::registry; use sqlbuild_core::constants::PROJECT_CONFIG_FILENAME; const LOCAL_CONFIG_FILENAME: &str = "sqlbuild_local.toml"; const SQL_ANALYSIS_KEY: &str = "sql_analysis"; @@ -29,8 +27,7 @@ fn project_config_error(py: Python<'_>, message: &str, help: Option<&str>) -> Py let raised = (|| -> PyResult> { let keywords: Bound<'_, PyDict> = PyDict::new(py); keywords.set_item("help", help)?; - imported(py, DISCOVERY_ERRORS_MODULE, "ProjectConfigError")? - .call((message,), Some(&keywords)) + registry::error(py, ErrorId::ProjectConfig)?.call((message,), Some(&keywords)) })(); match raised { Ok(instance) => PyErr::from_value(instance), @@ -66,7 +63,7 @@ fn reject_local_sql_analysis_opt_out(config: &ProjectConfiguration<'_, '_>) -> P local_settings.getattr(SQL_ANALYSIS_KEY)?, ], )?; - imported(py, NATIVE_MODULE, "validate_local_sql_analysis")? + registry::native(py, NativeAttr::ValidateLocalSqlAnalysis)? .call1((config.project_dir, flags))?; Ok(()) } @@ -94,11 +91,7 @@ fn session_warehouse_support( project_dir: &Bound<'_, PyAny>, ) -> PyResult { call_with( - &imported( - py, - SESSION_WAREHOUSE_MODULE, - "adapter_supports_session_warehouse", - )?, + &host::entry(py, AdapterHost::AdapterSupportsSessionWarehouse)?, &[ ("adapter_name", adapter_name.clone()), ("project_dir", project_dir.clone()), @@ -114,7 +107,7 @@ fn reject_group( (target, group, value): (String, String, String), ) -> PyErr { let raised = (|| -> PyResult { - let file_path: String = imported(py, NATIVE_MODULE, "resolve_config_file_path")? + let file_path: String = registry::native(py, NativeAttr::ResolveConfigFilePath)? .call1((project_dir, local))? .str()? .extract()?; @@ -151,7 +144,7 @@ fn reject_unsupported_target_warehouses(config: &ProjectConfiguration<'_, '_>) - return Ok(()); }; let adapter_name: Bound<'_, PyAny> = call_with( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", config.project_config.clone()), ("local_config", config.local_config.clone()), @@ -175,8 +168,8 @@ fn reject_invalid_target_connections(config: &ProjectConfiguration<'_, '_>) -> P names.extend(target_names(config.local_config)?); names.sort(); names.dedup(); - let resolve: Bound<'_, PyAny> = imported(py, TARGET_CONFIG_MODULE, "resolve_target_config")?; - let spec_error: Bound<'_, PyAny> = imported(py, SPEC_ERRORS_MODULE, "SpecConfigError")?; + let resolve: Bound<'_, PyAny> = registry::native(py, NativeAttr::ResolveTargetConfig)?; + let spec_error: Bound<'_, PyAny> = registry::error(py, ErrorId::SpecConfig)?; for name in names { let resolved: PyResult> = call_with( &resolve, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/deferral.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/deferral.rs index a07e375fd3..3f5f2f5bd3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/deferral.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/deferral.rs @@ -9,15 +9,10 @@ use pyo3::{pyfunction, wrap_pyfunction}; use sqlbuild_model_config::templates::main::deferred_target_segments::deferred_target_segments; use sqlbuild_model_config::templates::models::DeferredSegment; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; - -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const PLANNER_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; -const PROJECT_VARS_MODULE: &str = "sqlbuild._native"; const PRESERVE_TARGET_VALUE: &str = "preserve"; /// `sorted(DEFERRED_TARGET_CONTEXT_KEYS)`. const DEFERRED_TARGET_CONTEXT_KEYS: [&str; 2] = ["database", "schema"]; @@ -117,7 +112,7 @@ fn deferred_location<'py>( ], )?; call_with( - &imported(py, COMPILE_MODELS_MODULE, "CompiledRelationLocation")?, + ®istry::class(py, RecordId::CompiledRelationLocation)?, &[ ("database", database), ("schema", schema), @@ -155,7 +150,7 @@ fn target_field<'py>( .iter() .map(|key| (*key).to_owned()) .collect(); - let render: Bound<'py, PyAny> = imported(py, PROJECT_VARS_MODULE, "render_project_var_text")?; + let render: Bound<'py, PyAny> = registry::native(py, NativeAttr::RenderProjectVarText)?; let mut resolved: String = String::new(); for segment in deferred_target_segments(&template, &logical, &context_keys, &variables) { match segment { @@ -182,9 +177,7 @@ fn target_field<'py>( /// A `PlannerInputError` with `message` and the default code. fn planner_error(py: Python<'_>, message: String) -> PyErr { - match imported(py, PLANNER_EXCEPTIONS_MODULE, "PlannerInputError") - .and_then(|class| class.call1((message,))) - { + match registry::error(py, ErrorId::PlannerInput).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } @@ -322,7 +315,7 @@ fn resolve_model_deferral_inputs<'py>( &[], ), )?; - let inputs: Bound<'py, PyAny> = imported(py, NATIVE_RECORDS_MODULE, "DeferralInputs")?; + let inputs: Bound<'py, PyAny> = registry::class(py, RecordId::DeferralInputs)?; let defer_to: Bound<'py, PyAny> = arguments.value("defer_to")?; if defer_to.is_none() { return inputs.call0(); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/diagnostic_collector.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/diagnostic_collector.rs index f2b8f5325c..b5fbf09e5b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/diagnostic_collector.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/diagnostic_collector.rs @@ -8,8 +8,9 @@ use pyo3::types::{PyDict, PyDictMethods, PyTuple}; use pyo3::{PyErr, pyclass, pyfunction, pymethods, wrap_pyfunction}; use crate::bindings::_helpers::compile_driver::python_calls::{context_variable, imported}; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; const CONTEXT_VARIABLE_NAME: &str = "sqlbuild_collected_compile_diagnostics"; static ACTIVE_COLLECTOR: PyOnceLock> = PyOnceLock::new(); @@ -122,8 +123,7 @@ pub(crate) fn report_compile_diagnostic( keywords.set_item("help", diagnostic.getattr("help")?)?; let message: String = format!("{}{pointer}", diagnostic.getattr("message")?.str()?); Err(PyErr::from_value( - imported(py, COMPILE_ERRORS_MODULE, "CompileInputError")? - .call((message,), Some(&keywords))?, + registry::error(py, ErrorId::CompileInput)?.call((message,), Some(&keywords))?, )) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/diff.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/diff.rs index 0787d960c5..19874d97ce 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/diff.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/diff.rs @@ -16,9 +16,9 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, keywords, }; use crate::bindings::_helpers::graph::views::project_lineage_views; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const PLANNER_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; /// Indexes of the `CompiledObjectKey`s a diff graph names, assigned on first sight. @@ -111,7 +111,7 @@ fn diff_graph<'py>(project: &Bound<'py, PyAny>, keys: &KeyIndexes<'py>) -> PyRes /// The `PlannerInputError` a rejected selector raises. fn selector_error(py: Python<'_>, error: SelectorError) -> PyErr { let raised: PyResult> = (|| { - imported(py, PLANNER_EXCEPTIONS_MODULE, "PlannerInputError")?.call( + registry::error(py, ErrorId::PlannerInput)?.call( (error.message,), Some(&keywords( py, @@ -202,7 +202,7 @@ fn run_diff_pipeline<'py>( &["cli_vars", "external_sql_reference_resolver"], ), )?; - let build: Bound<'py, PyAny> = imported(py, NATIVE_MODULE, "build_compiled_project")?; + let build: Bound<'py, PyAny> = registry::native(py, NativeAttr::BuildCompiledProject)?; let compile = |target: &str| -> PyResult> { call_with( &build, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/discovery.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/discovery.rs index 36d097f030..09b049f239 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/discovery.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/discovery.rs @@ -9,7 +9,7 @@ use crate::bindings::_helpers::compile_driver::config_validation::{ }; use crate::bindings::_helpers::compile_driver::integration_loaders::integration_loader_functions; use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, keywords, within, + KeywordArguments, call_with, keywords, within, }; use crate::bindings::_helpers::discovery::declaration_records::{ discover_adapter_file, discover_audit_files, discover_constant_files, discover_enum_files, @@ -29,16 +29,13 @@ use crate::bindings::_helpers::discovery::yaml_records::{ discover_schema_files, discover_source_files, }; use crate::bindings::_helpers::event_exporting::scope::configure_discovered_event_exporters; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const FRONTIER_MODULE: &str = "sqlbuild._native"; -const FRONTIER_TYPES_MODULE: &str = "sqlbuild._native"; -const LIFECYCLE_MODULE: &str = "sqlbuild.runtime.observability.classes.operation_lifecycle"; -const VALIDATION_MODULE: &str = "sqlbuild.compiler.discovery._helpers.validation.discovery"; -const NATIVE_MODULE: &str = "sqlbuild._native"; const SQL_ANALYSIS_SETTING_KEY: &str = "sql_analysis"; -const FILESYSTEM_MODULE: &str = "sqlbuild._native"; -const SINKS_MODULE: &str = "sqlbuild.compiler.discovery._helpers.filesystem.command_output_sinks"; -const DISCOVERY_MODELS_MODULE: &str = "sqlbuild._native"; /// Declaration file collections counted into the declaration-parse lifecycle, in field order. const DECLARATION_COLLECTIONS: [&str; 14] = [ @@ -102,41 +99,52 @@ const FILE_DISCOVERERS: [(&str, FileDiscoverer); 14] = [ ]; /// Discoverers of imported project Python that take `project_dir` and `providers`. -const PYTHON_DISCOVERERS: [(&str, &str, &str); 5] = [ +const PYTHON_DISCOVERERS: [(&str, Discoverer); 5] = [ ( "python_nodes", - FILESYSTEM_MODULE, - "discover_python_node_functions", + Discoverer::Native(NativeAttr::DiscoverPythonNodeFunctions), ), ( "materialization_files", - FILESYSTEM_MODULE, - "discover_materialization_files", + Discoverer::Native(NativeAttr::DiscoverMaterializationFiles), ), ( "hook_functions", - FILESYSTEM_MODULE, - "discover_hook_functions", + Discoverer::Native(NativeAttr::DiscoverHookFunctions), ), ( "event_exporters", - FILESYSTEM_MODULE, - "discover_event_exporter_functions", + Discoverer::Native(NativeAttr::DiscoverEventExporterFunctions), ), ( "command_output_sinks", - SINKS_MODULE, - "discover_command_output_sink_functions", + Discoverer::Host(UserCodeHost::DiscoverCommandOutputSinkFunctions), ), ]; +/// Where a project-Python discoverer lives. +#[derive(Clone, Copy)] +enum Discoverer { + Native(NativeAttr), + Host(UserCodeHost), +} + +impl Discoverer { + fn function<'py>(self, py: Python<'py>) -> PyResult> { + match self { + Self::Native(id) => registry::native(py, id), + Self::Host(entry) => host::entry(py, entry), + } + } +} + /// Python node groups whose relative paths count as imported project Python. const PYTHON_NODE_GROUPS: [&str; 5] = ["loaders", "tasks", "assets", "checks", "audit_factories"]; /// `OperationLifecycle(operation_kind="project", operation_name=name)`. fn lifecycle<'py>(py: Python<'py>, name: &str) -> PyResult> { call_with( - &imported(py, LIFECYCLE_MODULE, "OperationLifecycle")?, + &host::entry(py, ExecutorHost::OperationLifecycle)?, &[ ("operation_kind", "project".into_pyobject(py)?.into_any()), ("operation_name", name.into_pyobject(py)?.into_any()), @@ -190,11 +198,11 @@ fn discover_project_inputs<'py>( }, )?; call_with( - &imported(py, FRONTIER_MODULE, "compile_frontier")?, + ®istry::native(py, NativeAttr::CompileFrontier)?, &[ ( "until", - imported(py, FRONTIER_TYPES_MODULE, "CompilerStage")? + registry::class(py, RecordId::CompilerStage)? .getattr("DISCOVERED_PROJECT_INPUTS")?, ), ("python_stage", stage.into_any()), @@ -210,9 +218,9 @@ fn assembled_inputs<'py>( ) -> PyResult> { let project_dir: &Bound<'py, PyAny> = request.project_dir.bind(py); let project_config: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "load_project_config_file")?.call1((project_dir,))?; + registry::native(py, NativeAttr::LoadProjectConfigFile)?.call1((project_dir,))?; let local_config: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "load_local_config_file")?.call1((project_dir,))?; + registry::native(py, NativeAttr::LoadLocalConfigFile)?.call1((project_dir,))?; reject_invalid_project_configuration(&ProjectConfiguration { project_dir, project_config: &project_config, @@ -240,7 +248,7 @@ fn assembled_inputs<'py>( request.extract_output_column_locations, ), )?; - imported(py, VALIDATION_MODULE, "validate_discovered_inputs")?.call1((&discovered,))?; + host::entry(py, UserCodeHost::ValidateDiscoveredInputs)?.call1((&discovered,))?; configure_discovered_event_exporters(&discovered)?; Ok(discovered) } @@ -364,16 +372,16 @@ fn declaration_files<'py>( fn imported_python<'py>(project_dir: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = project_dir.py(); let providers: Bound<'py, PyAny> = call_with( - &imported(py, FILESYSTEM_MODULE, "discover_provider_classes")?, + ®istry::native(py, NativeAttr::DiscoverProviderClasses)?, &[("project_dir", project_dir.clone())], )?; let python: Bound<'py, PyDict> = PyDict::new(py); python.set_item("providers", &providers)?; - for (field, module, name) in PYTHON_DISCOVERERS { + for (field, discoverer) in PYTHON_DISCOVERERS { python.set_item( field, call_with( - &imported(py, module, name)?, + &discoverer.function(py)?, &[ ("project_dir", project_dir.clone()), ("providers", providers.clone()), @@ -480,7 +488,7 @@ fn project_inputs<'py>( fields.push(("adapter_file", declarations.get_item("adapter_file")?)); fields.push(("native_session", declarations.get_item("native_session")?)); call_with( - &imported(py, DISCOVERY_MODELS_MODULE, "DiscoveredProjectInputs")?, + ®istry::class(py, RecordId::DiscoveredProjectInputs)?, &fields, ) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/inputs.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/inputs.rs index 95088a29f3..9b41b1d58b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/inputs.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/inputs.rs @@ -8,19 +8,10 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, keywords, within, }; use crate::bindings::_helpers::model_config::template_data::run_id; - -const CALL_STORE_MODULE: &str = "sqlbuild.compiler.compile._helpers.macro_bridge.call_store"; -const ATTACHMENT_MODULE: &str = "sqlbuild._native"; -const DECLARATION_SCOPE_MODULE: &str = "sqlbuild._native"; -const COLLECTOR_MODULE: &str = "sqlbuild._native"; -const MACROS_MODULE: &str = "sqlbuild.compiler.compile._helpers.render.user_macros"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; -const ATTACH_CHECKS_MODULE: &str = "sqlbuild._native"; -const ATTACH_RESOURCES_MODULE: &str = "sqlbuild._native"; -const INPUTS_VIEW_MODULE: &str = "sqlbuild._native"; -const INPUTS_WITH_MODULE: &str = "sqlbuild._native"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; -const NATIVE_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; const REQUIRED: [&str; 2] = ["discovered_inputs", "adapter_context"]; const OPTIONAL: [&str; 8] = [ @@ -71,7 +62,7 @@ fn build_compile_inputs<'py>( let arguments: KeywordArguments<'py> = KeywordArguments::read(py, "build_compile_inputs", options, (&REQUIRED, &OPTIONAL))?; let collector: Bound<'py, PyAny> = - imported(py, COLLECTOR_MODULE, "collect_compile_diagnostics")?.call0()?; + registry::native(py, NativeAttr::CollectCompileDiagnostics)?.call0()?; let (built, violations): (Bound<'py, PyAny>, Bound<'py, PyAny>) = within(&collector, |violations| { Ok((compile_inputs(py, &arguments)?, violations.clone())) @@ -89,7 +80,7 @@ fn build_compile_inputs<'py>( .collect::>>()?, )?; call_with( - &imported(py, INPUTS_WITH_MODULE, "inputs_with")?, + ®istry::native(py, NativeAttr::InputsWith)?, &[("inputs", built), ("diagnostics", diagnostics.into_any())], ) } @@ -103,11 +94,11 @@ fn compile_inputs<'py>( let adapter_context: Bound<'py, PyAny> = arguments.value("adapter_context")?; let target: TargetContext<'py> = target_context(arguments, &discovered, &adapter_context)?; let run_id: Bound<'py, PyAny> = run_id(&arguments.value("run_id")?)?; - let loaded_macros: Bound<'py, PyAny> = imported(py, MACROS_MODULE, "load_project_macros")? + let loaded_macros: Bound<'py, PyAny> = host::entry(py, UserCodeHost::LoadProjectMacros)? .call1((discovered.getattr("macro_files")?,))?; let syntax: Bound<'py, PyAny> = adapter_context.getattr("sql_lexical_syntax")?; let declaration_scope: Bound<'py, PyAny> = call_with( - &imported(py, DECLARATION_SCOPE_MODULE, "build_declaration_scope")?, + ®istry::native(py, NativeAttr::BuildDeclarationScope)?, &[ ("discovered_inputs", discovered.clone()), ("loaded_macros", loaded_macros), @@ -117,7 +108,7 @@ fn compile_inputs<'py>( )?; let value_renderer: Bound<'py, PyAny> = adapter_context.getattr("value_renderer")?; let model_context: Bound<'py, PyAny> = call_with( - &imported(py, COMPILE_MODELS_MODULE, "ModelInputBuildContext")?, + ®istry::class(py, RecordId::ModelInputBuildContext)?, &[ ("effective_vars", target.vars.clone()), ("effective_settings", target.settings.clone()), @@ -187,7 +178,7 @@ fn compile_inputs<'py>( ], )?; call_with( - &imported(py, INPUTS_VIEW_MODULE, "compile_inputs_view")?, + ®istry::native(py, NativeAttr::CompileInputsView)?, &[ ("fields", fields.into_any()), ("native_project", models.native_project), @@ -203,11 +194,7 @@ fn target_context<'py>( ) -> PyResult> { let py: Python<'py> = discovered.py(); let selected: Bound<'py, PyAny> = call_with( - &imported( - py, - CALL_STORE_MODULE, - "target_context_with_macro_call_store", - )?, + &host::entry(py, UserCodeHost::TargetContextWithMacroCallStore)?, &[ ("discovered_inputs", discovered.clone()), ("selected_target", arguments.value("selected_target")?), @@ -225,7 +212,7 @@ fn target_context<'py>( cli_vars }; let vars: Bound<'py, PyAny> = call_with( - &imported(py, ATTACHMENT_MODULE, "build_effective_vars")?, + ®istry::native(py, NativeAttr::BuildEffectiveVars)?, &[ ("project_config", project_config.clone()), ("local_config", local_config.clone()), @@ -234,21 +221,21 @@ fn target_context<'py>( ], )?; let settings: Bound<'py, PyAny> = call_with( - &imported(py, ATTACHMENT_MODULE, "build_effective_settings")?, + ®istry::native(py, NativeAttr::BuildEffectiveSettings)?, &[ ("project_config", project_config.clone()), ("local_config", local_config.clone()), ], )?; let adapter_name: Bound<'py, PyAny> = call_with( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", project_config.clone()), ("local_config", local_config), ], )?; let macro_context: Bound<'py, PyAny> = call_with( - &imported(py, COMPILE_MODELS_MODULE, "MacroContext")?, + ®istry::class(py, RecordId::MacroContext)?, &[ ("adapter_name", adapter_name), ("sql_analysis_enabled", settings.getattr("sql_analysis")?), @@ -287,16 +274,14 @@ fn model_build<'py>( context: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = discovered.py(); - let native: Bound<'py, PyModule> = py.import(NATIVE_MODULE)?; - let declarations: Bound<'py, PyAny> = native - .getattr("public_declaration_indexes")? - .call1((discovered,))?; + let declarations: Bound<'py, PyAny> = + registry::native(py, NativeAttr::PublicDeclarationIndexes)?.call1((discovered,))?; let enums: Bound<'py, PyAny> = declarations.get_item(0)?; let constants: Bound<'py, PyAny> = declarations.get_item(1)?; - let schemas: Bound<'py, PyAny> = native - .getattr("public_model_schema_index")? - .call1((discovered,))?; - let native_project: Bound<'py, PyAny> = native.getattr("NativeCompiledProject")?.call0()?; + let schemas: Bound<'py, PyAny> = + registry::native(py, NativeAttr::PublicModelSchemaIndex)?.call1((discovered,))?; + let native_project: Bound<'py, PyAny> = + registry::native(py, NativeAttr::NativeCompiledProject)?.call0()?; let replace: Bound<'py, PyAny> = imported(py, "dataclasses", "replace")?; let model_context: Bound<'py, PyAny> = replace.call( (context,), @@ -310,7 +295,7 @@ fn model_build<'py>( )?), )?; let inputs: Bound<'py, PyAny> = call_with( - &imported(py, ATTACHMENT_MODULE, "build_model_inputs")?, + ®istry::native(py, NativeAttr::BuildModelInputs)?, &[ ("discovered_inputs", discovered.clone()), ("context", model_context), @@ -359,7 +344,7 @@ fn attached_resources<'py>( let context: &Bound<'py, PyAny> = &models.context; let settings: Bound<'py, PyAny> = context.getattr("effective_settings")?; let resources: Bound<'py, PyAny> = call_with( - &imported(py, ATTACH_RESOURCES_MODULE, "attach_resources")?, + ®istry::native(py, NativeAttr::AttachResources)?, &[ ("discovered_inputs", discovered.clone()), ("native_project", models.native_project.clone()), @@ -387,7 +372,7 @@ fn attached_resources<'py>( )?; let sources: Bound<'py, PyAny> = resources.get_item(2)?; let checks: Bound<'py, PyAny> = call_with( - &imported(py, ATTACH_CHECKS_MODULE, "attach_checks")?, + ®istry::native(py, NativeAttr::AttachChecks)?, &[ ("discovered_inputs", discovered.clone()), ("native_project", models.native_project.clone()), @@ -426,11 +411,7 @@ fn effective_connection<'py>( return Ok(resolved); } call_with( - &imported( - discovered.py(), - ATTACHMENT_MODULE, - "build_effective_connection", - )?, + ®istry::native(discovered.py(), NativeAttr::BuildEffectiveConnection)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/integration_loaders.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/integration_loaders.rs index 1ac19d99e2..77adbbe5b2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/integration_loaders.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/integration_loaders.rs @@ -5,32 +5,27 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::PyTuple; use sqlbuild_discovery::yaml_entries::main::integration_loader_name::integration_loader_name; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; - -const DISCOVERY_MODELS_MODULE: &str = "sqlbuild._native"; -const DISCOVERY_TYPES_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::third_party::ThirdPartyHost; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; /// One integration: its kind constant and the runtime module building its loader functions. struct Integration { - constants_module: &'static str, - kind_constant: &'static str, - loaders_module: &'static str, - loader_factory: &'static str, + kind_constant: ThirdPartyHost, + loader_factory: ThirdPartyHost, } /// Integrations in loader order: every ingestr loader precedes every dlt loader. const INTEGRATIONS: [Integration; 2] = [ Integration { - constants_module: "sqlbuild.integrations.ingestr.constants", - kind_constant: "INGESTR_INTEGRATION_KIND", - loaders_module: "sqlbuild.integrations.ingestr.main._loaders", - loader_factory: "ingestr_loader_function", + kind_constant: ThirdPartyHost::IngestrIntegrationKind, + loader_factory: ThirdPartyHost::IngestrLoaderFunction, }, Integration { - constants_module: "sqlbuild.integrations.dlt.constants", - kind_constant: "DLT_INTEGRATION_KIND", - loaders_module: "sqlbuild.integrations.dlt.main._loaders", - loader_factory: "dlt_loader_function", + kind_constant: ThirdPartyHost::DltIntegrationKind, + loader_factory: ThirdPartyHost::DltLoaderFunction, }, ]; @@ -39,16 +34,13 @@ pub(crate) fn integration_loader_functions<'py>( py: Python<'py>, source_files: &Bound<'py, PyAny>, ) -> PyResult> { - let record_class: Bound<'py, PyAny> = - imported(py, DISCOVERY_MODELS_MODULE, "DiscoveredLoaderFunction")?; + let record_class: Bound<'py, PyAny> = registry::class(py, RecordId::DiscoveredLoaderFunction)?; let external: Bound<'py, PyAny> = - imported(py, DISCOVERY_TYPES_MODULE, "LoaderConnectionMode")?.getattr("EXTERNAL")?; + registry::class(py, RecordId::LoaderConnectionMode)?.getattr("EXTERNAL")?; let mut loaders: Vec> = Vec::new(); for integration in &INTEGRATIONS { - let kind: String = - imported(py, integration.constants_module, integration.kind_constant)?.extract()?; - let factory: Bound<'py, PyAny> = - imported(py, integration.loaders_module, integration.loader_factory)?; + let kind: String = host::entry(py, integration.kind_constant)?.extract()?; + let factory: Bound<'py, PyAny> = host::entry(py, integration.loader_factory)?; for source_file in source_files.try_iter()? { let source_file: Bound<'py, PyAny> = source_file?; for source_entry in source_file.getattr("source_entries")?.try_iter()? { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/phases.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/phases.rs index 3f400630ca..234514b0e5 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/phases.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/phases.rs @@ -12,15 +12,9 @@ use crate::bindings::_helpers::compile_command::target_writer::{ use crate::bindings::_helpers::compile_driver::python_calls::{Status, call_with, imported}; use crate::bindings::_helpers::dag_artifact::artifact::build_dag_json; use crate::bindings::_helpers::graph::python_node_graph::python_node_graph; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; -const OUTPUT_MODELS_MODULE: &str = "sqlbuild._native"; -const COMPILE_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; -const PLANNER_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const EMPTY_DAG_PATH: &str = ""; const TARGET_DIRECTORY: &str = "target"; const DEFAULT_DAG_FILE: &str = "sqlbuild_dag.json"; @@ -62,14 +56,14 @@ pub(crate) fn manifest_payload<'py>( let project_config: Bound<'py, PyAny> = discovered.getattr("project_config")?; let graph: Bound<'py, PyAny> = analysis.getattr("graph")?; let adapter_type: Bound<'py, PyAny> = call_with( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", project_config.clone()), ("local_config", discovered.getattr("local_config")?), ], )?; let payload: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "build_manifest")?, + ®istry::native(py, NativeAttr::BuildManifest)?, &[ ("project", graph.getattr("project")?), ("plan_output", py.None().into_bound(py)), @@ -166,31 +160,28 @@ pub(crate) fn sql_test_planning_diagnostics<'py>( Ok(diagnostics) => return Ok(diagnostics), Err(error) => error, }; - let compile_input_error: Bound<'py, PyAny> = - imported(py, COMPILE_EXCEPTIONS_MODULE, "CompileInputError")?; - let planner_input_error: Bound<'py, PyAny> = - imported(py, PLANNER_EXCEPTIONS_MODULE, "PlannerInputError")?; + let compile_input_error: Bound<'py, PyAny> = registry::error(py, ErrorId::CompileInput)?; + let planner_input_error: Bound<'py, PyAny> = registry::error(py, ErrorId::PlannerInput)?; let value: Bound<'py, PyAny> = error.value(py).clone().into_any(); if !(value.is_instance(&compile_input_error)? || value.is_instance(&planner_input_error)?) { return Err(error); } - let reference_error: Bound<'py, PyAny> = - imported(py, COMPILE_EXCEPTIONS_MODULE, "SqlTestReferenceError")?; + let reference_error: Bound<'py, PyAny> = registry::error(py, ErrorId::SqlTestReference)?; let location: Bound<'py, PyAny> = if value.is_instance(&reference_error)? { value.getattr("location")? } else { py.None().into_bound(py) }; let diagnostic: Bound<'py, PyAny> = call_with( - &imported(py, COMPILE_MODELS_MODULE, "CompilerDiagnostic")?, + ®istry::class(py, RecordId::CompilerDiagnostic)?, &[ ( "phase", - imported(py, NATIVE_RECORDS_MODULE, "DiagnosticPhase")?.getattr("TEST")?, + registry::class(py, RecordId::DiagnosticPhase)?.getattr("TEST")?, ), ( "severity", - imported(py, NATIVE_RECORDS_MODULE, "DiagnosticSeverity")?.getattr("ERROR")?, + registry::class(py, RecordId::DiagnosticSeverity)?.getattr("ERROR")?, ), ("code", value.getattr("code")?), ("message", value.str()?.into_any()), @@ -214,7 +205,7 @@ pub(crate) fn write_artifacts<'py>( let written: Bound<'py, PyAny> = if skip_write { let zero: Bound<'py, PyAny> = 0_i64.into_pyobject(py)?.into_any(); call_with( - &imported(py, OUTPUT_MODELS_MODULE, "WrittenTarget")?, + ®istry::class(py, RecordId::WrittenTarget)?, &[ ("model_count", zero.clone()), ("seed_count", zero.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/pipeline.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/pipeline.rs index 4f929f4271..f79c418d9b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/pipeline.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/pipeline.rs @@ -15,26 +15,19 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, keywords, }; use crate::bindings::_helpers::frontier::stage_capture::write_plan_capture; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::results::{format_rules_result, rules_progress}; use crate::bindings::_helpers::rules::rules_config::load_rules_config; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const EFFECTIVE_CONFIG_MODULE: &str = "sqlbuild._native"; -const COMPILE_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; -const ANALYSIS_SELECTION_MODULE: &str = "sqlbuild._native"; -const TIMING_TRACKER_MODULE: &str = "sqlbuild._native"; -const RULE_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; -const RULE_THREADS_MODULE: &str = "sqlbuild._native"; -const RULE_MODELS_MODULE: &str = "sqlbuild._native"; -const RUNTIME_MODELS_MODULE: &str = "sqlbuild.runtime.contracts.models"; const GENERIC_DIALECT: &str = "generic"; /// The current build's phase timing tracker, when one is recording. fn timing_tracker(py: Python<'_>) -> PyResult> { - imported(py, TIMING_TRACKER_MODULE, "BuildPhaseTimingTracker")?.call_method0("current") + registry::native(py, NativeAttr::BuildPhaseTimingTracker)?.call_method0("current") } /// Report `message` through `on_progress` when a callback is set. @@ -81,16 +74,12 @@ fn compiled_phase<'py>( let options: Bound<'py, PyAny> = if options.is_truthy()? { options.clone() } else { - imported(py, NATIVE_RECORDS_MODULE, "CompilePipelineOptions")?.call0()? + registry::class(py, RecordId::CompilePipelineOptions)?.call0()? }; let configured: Bound<'py, PyAny> = options.getattr("connection_config")?; let connection_config: Bound<'py, PyAny> = if configured.is_none() { call_with( - &imported( - py, - EFFECTIVE_CONFIG_MODULE, - "build_effective_connection_config", - )?, + ®istry::native(py, NativeAttr::BuildEffectiveConnectionConfig)?, &[ ("discovered_inputs", discovered.clone()), ("selected_target", options.getattr("selected_target")?), @@ -105,7 +94,7 @@ fn compiled_phase<'py>( let compiled: PyResult> = (|| { let selection: Bound<'py, PyAny> = compile_analysis_selection(&options, discovered)?; call_with( - &imported(py, NATIVE_MODULE, "build_compiled_project")?, + ®istry::native(py, NativeAttr::BuildCompiledProject)?, &[ ("discovered_inputs", discovered.clone()), ("adapter", adapter.clone()), @@ -133,7 +122,7 @@ fn compiled_phase<'py>( &format!("Compiled project. ({compile_seconds:.2}s)"), )?; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "CompiledProjectPhaseResult")?, + ®istry::class(py, RecordId::CompiledProjectPhaseResult)?, &[ ("project", project), ("connection_config", connection_config), @@ -194,14 +183,13 @@ fn reject_compile_errors( } else { py.None().into_bound(py) }; - let error: Bound<'_, PyAny> = imported(py, COMPILE_EXCEPTIONS_MODULE, "CompileInputError")? - .call( - (lines.join("\n"),), - Some(&keywords( - py, - &[("code", first.getattr("code")?), ("help", help)], - )?), - )?; + let error: Bound<'_, PyAny> = registry::error(py, ErrorId::CompileInput)?.call( + (lines.join("\n"),), + Some(&keywords( + py, + &[("code", first.getattr("code")?), ("help", help)], + )?), + )?; Err(PyErr::from_value(error)) } @@ -221,7 +209,7 @@ fn run_compile_pipeline<'py>( let discovered: Bound<'py, PyAny> = arguments.value("discovered_inputs")?; let adapter: Bound<'py, PyAny> = arguments.value("adapter")?; let options: Bound<'py, PyAny> = call_with( - &imported(py, ANALYSIS_SELECTION_MODULE, "apply_unit_test_selectors")?, + ®istry::native(py, NativeAttr::ApplyUnitTestSelectors)?, &[ ("options", arguments.value("options")?), ("discovered_inputs", discovered.clone()), @@ -229,7 +217,7 @@ fn run_compile_pipeline<'py>( )?; let hooks: Bound<'py, PyAny> = arguments.value("hooks")?; let hooks: Bound<'py, PyAny> = if hooks.is_none() { - imported(py, RUNTIME_MODELS_MODULE, "ConnectionHooks")?.call0()? + host::entry(py, ExecutorHost::ConnectionHooks)?.call0()? } else { hooks }; @@ -253,7 +241,7 @@ fn run_rules_gate<'py>( (project, graph): (&Bound<'py, PyAny>, &Bound<'py, PyAny>), ) -> PyResult<()> { let py: Python<'py> = discovered.py(); - let rules_error: Bound<'py, PyAny> = imported(py, RULE_EXCEPTIONS_MODULE, "RulesError")?; + let rules_error: Bound<'py, PyAny> = registry::error(py, ErrorId::Rules)?; let project_dir: Bound<'py, PyAny> = discovered.getattr("project_dir")?; if project_dir.is_none() { return Err(PyErr::from_value( @@ -264,7 +252,7 @@ fn run_rules_gate<'py>( load_rules_config(py, project_dir.extract()?)?.into_bound(py); if options.getattr("no_cache")?.is_truthy()? { let cache: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "RulesCacheConfig")?, + ®istry::class(py, RecordId::RulesCacheConfig)?, &[("enabled", false.into_pyobject(py)?.to_owned().into_any())], )?; config = imported(py, "dataclasses", "replace")? @@ -279,11 +267,7 @@ fn run_rules_gate<'py>( GENERIC_DIALECT.into_pyobject(py)?.into_any() }; let selected_keys: Bound<'py, PyAny> = call_with( - &imported( - py, - ANALYSIS_SELECTION_MODULE, - "resolve_configured_rule_selection", - )?, + ®istry::native(py, NativeAttr::ResolveConfiguredRuleSelection)?, &[ ("options", options.clone()), ("discovered_inputs", discovered.clone()), @@ -291,7 +275,7 @@ fn run_rules_gate<'py>( ], )?; let result: Bound<'py, PyAny> = call_with( - &imported(py, RULE_THREADS_MODULE, "evaluate_rules")?, + ®istry::native(py, NativeAttr::EvaluateRules)?, &[ ("graph", graph.clone()), ("discovered_inputs", discovered.clone()), @@ -312,7 +296,7 @@ fn run_rules_gate<'py>( return Ok(()); } let summary: Bound<'py, PyAny> = call_with( - &imported(py, RULE_MODELS_MODULE, "RulesResult")?, + ®istry::class(py, RecordId::RulesResult)?, &[ ("findings", findings), ("evaluated_models", result.getattr("evaluated_models")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/pipeline_entries.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/pipeline_entries.rs index 5b964605ec..9ffdda1e23 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/pipeline_entries.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/pipeline_entries.rs @@ -4,15 +4,10 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyRes use pyo3::types::{PyDict, PyFloat}; use pyo3::{pyfunction, wrap_pyfunction}; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const PIPELINE_MODELS_MODULE: &str = "sqlbuild._native"; -const PLANNER_MODELS_MODULE: &str = "sqlbuild._native"; -const NATIVE_MODULE: &str = "sqlbuild._native"; /// Discovered Python node functions whose selectors need the Python graph. const PYTHON_NODE_FUNCTIONS: [&str; 4] = [ "loader_functions", @@ -62,8 +57,7 @@ pub(crate) fn compile_analysis_selection<'py>( break; } } - let selection: Bound<'py, PyAny> = - imported(py, NATIVE_RECORDS_MODULE, "CompileAnalysisSelection")?; + let selection: Bound<'py, PyAny> = registry::class(py, RecordId::CompileAnalysisSelection)?; let no_cache: Bound<'py, PyAny> = options.getattr("no_cache")?; if (options .getattr("resolve_python_run_selectors")? @@ -106,7 +100,7 @@ fn build_project_graph_from_compiled_project<'py>( /// The project's natively held graph; dict views are built only when a boundary reads them. pub(crate) fn project_graph<'py>(project: &Bound<'py, PyAny>) -> PyResult> { call_with( - &imported(project.py(), PIPELINE_MODELS_MODULE, "ProjectGraph")?, + ®istry::class(project.py(), RecordId::ProjectGraph)?, &[ ("project", project.clone()), ("native", project.getattr("lineage_graph")?), @@ -154,7 +148,7 @@ fn build_static_pipeline_result<'py>( )?; let context: Bound<'py, PyAny> = arguments.value("context")?; call_with( - &imported(py, PIPELINE_MODELS_MODULE, "CompilePipelineResult")?, + ®istry::class(py, RecordId::CompilePipelineResult)?, &[ ("project", context.getattr("project")?), ("plan_output", arguments.value("plan_output")?), @@ -181,7 +175,7 @@ fn run_compile_only_pipeline<'py>( ), )?; let compiled: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "compile_project_phase")?, + ®istry::native(py, NativeAttr::CompileProjectPhase)?, &[ ("discovered_inputs", arguments.value("discovered_inputs")?), ("adapter", arguments.value("adapter")?), @@ -190,12 +184,12 @@ fn run_compile_only_pipeline<'py>( ], )?; call_with( - &imported(py, PIPELINE_MODELS_MODULE, "CompilePipelineResult")?, + ®istry::class(py, RecordId::CompilePipelineResult)?, &[ ("project", compiled.getattr("project")?), ( "plan_output", - imported(py, PLANNER_MODELS_MODULE, "PlanOutput")?.call0()?, + registry::class(py, RecordId::PlanOutput)?.call0()?, ), ("compile_seconds", compiled.getattr("compile_seconds")?), ("planning_seconds", PyFloat::new(py, 0.0).into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/plan_result.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/plan_result.rs index a2660a48e6..142b6a0df1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/plan_result.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/plan_result.rs @@ -1,23 +1,18 @@ //! Plan a compiled project over an open warehouse connection, and open that connection. -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class; use pyo3::exceptions::PyException; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyFrozenSet}; use pyo3::{pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::compile_driver::phases::monotonic; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::graph::python_node_graph::run_selection_from_inputs; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const EXECUTION_PLAN_MODULE: &str = "sqlbuild._native"; -const MATERIALIZATIONS_MODULE: &str = "sqlbuild.compiler.pipeline._helpers.materializations"; -const PIPELINE_MODELS_MODULE: &str = "sqlbuild._native"; const SINGLE_CONNECTION: i64 = 1; /// The deferred relation locations and the warehouse relations behind them, when deferring. @@ -38,7 +33,6 @@ pub(crate) fn pipeline_result<'py>( on_progress: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = project.py(); - let native = py.import(NATIVE_MODULE)?; let discovered: &Bound<'py, PyAny> = inputs.discovered_inputs; let select: Bound<'py, PyAny> = options.getattr("select")?; let exclude: Bound<'py, PyAny> = options.getattr("exclude")?; @@ -57,7 +51,7 @@ pub(crate) fn pipeline_result<'py>( .is_truthy()? { let graph: Bound<'py, PyAny> = call_with( - &native.getattr("build_project_graph_from_compiled_project")?, + ®istry::native(py, NativeAttr::BuildProjectGraphFromCompiledProject)?, &[("project", project.clone())], )?; run_selection = @@ -65,16 +59,15 @@ pub(crate) fn pipeline_result<'py>( selected_sql_keys = run_selection.getattr("sql_keys")?; selected_python_node_names = run_selection.getattr("python_node_names")?; python_read_source_names = call_with( - &native.getattr("python_read_source_names_for")?, + ®istry::native(py, NativeAttr::PythonReadSourceNamesFor)?, &[ ("discovered_inputs", discovered.clone()), ("python_node_names", selected_python_node_names.clone()), ], )?; } - let planner_models = py.import(NATIVE_RECORDS_MODULE)?; let selection: Bound<'py, PyAny> = call_with( - &planner_models.getattr("PlannerSelection")?, + ®istry::class(py, RecordId::PlannerSelection)?, &[ ("select", select), ("exclude", exclude), @@ -84,7 +77,7 @@ pub(crate) fn pipeline_result<'py>( ], )?; let overrides: Bound<'py, PyAny> = call_with( - &shape_class(planner_models.as_any(), "PlannerOverrides")?, + ®istry::class(py, RecordId::PlannerOverrides)?, &option_fields( options, &[ @@ -104,11 +97,11 @@ pub(crate) fn pipeline_result<'py>( &["defer_sources_to", "source_deferral_enabled"], )?); let deferral: Bound<'py, PyAny> = call_with( - &shape_class(planner_models.as_any(), "DeferralInputs")?, + ®istry::class(py, RecordId::DeferralInputs)?, &deferral_fields, )?; let policies: Bound<'py, PyAny> = call_with( - &shape_class(planner_models.as_any(), "PlannerPolicies")?, + ®istry::class(py, RecordId::PlannerPolicies)?, &option_fields( options, &[ @@ -120,7 +113,7 @@ pub(crate) fn pipeline_result<'py>( )?, )?; let plan_output: Bound<'py, PyAny> = call_with( - &imported(py, EXECUTION_PLAN_MODULE, "build_execution_plan")?, + ®istry::native(py, NativeAttr::BuildExecutionPlan)?, &[ ("project", project.clone()), ("adapter", inputs.adapter.clone()), @@ -135,10 +128,10 @@ pub(crate) fn pipeline_result<'py>( ], )?; let custom_materializations: Bound<'py, PyAny> = - imported(py, MATERIALIZATIONS_MODULE, "load_custom_materializations")? + host::entry(py, UserCodeHost::LoadCustomMaterializations)? .call1((discovered.getattr("materialization_files")?,))?; let python_outputs: Bound<'py, PyAny> = call_with( - &native.getattr("build_python_run_plan_outputs")?, + ®istry::native(py, NativeAttr::BuildPythonRunPlanOutputs)?, &[ ("discovered_inputs", discovered.clone()), ("plan_output", plan_output), @@ -147,7 +140,7 @@ pub(crate) fn pipeline_result<'py>( ], )?; call_with( - &imported(py, PIPELINE_MODELS_MODULE, "CompilePipelineResult")?, + ®istry::class(py, RecordId::CompilePipelineResult)?, &[ ("project", project.clone()), ("plan_output", python_outputs.getattr("plan_output")?), @@ -185,10 +178,9 @@ fn deferral<'py>( if defer_to.is_none() { return Ok((py.None().into_bound(py), py.None().into_bound(py))); } - let native = py.import(NATIVE_MODULE)?; let adapter: &Bound<'py, PyAny> = inputs.adapter; let target_config: Bound<'py, PyAny> = call_with( - &native.getattr("resolve_deferred_target_config")?, + ®istry::native(py, NativeAttr::ResolveDeferredTargetConfig)?, &[ ("discovered_inputs", inputs.discovered_inputs.clone()), ("defer_to", defer_to.clone()), @@ -199,7 +191,7 @@ fn deferral<'py>( ], )?; let locations: Bound<'py, PyAny> = call_with( - &native.getattr("build_deferred_locations")?, + ®istry::native(py, NativeAttr::BuildDeferredLocations)?, &[ ("project", project.clone()), ("deferred_target_config", target_config), @@ -216,7 +208,7 @@ fn deferral<'py>( ], )?; let relations: Bound<'py, PyAny> = call_with( - &native.getattr("gather_deferred_relations")?, + ®istry::native(py, NativeAttr::GatherDeferredRelations)?, &[ ("adapter", adapter.clone()), ("connection", inputs.connection.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/project.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/project.rs index b82f324eb0..ce6ebd9b9e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/project.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/project.rs @@ -18,20 +18,11 @@ use crate::bindings::_helpers::compile_driver::targets::{ project_targets, }; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const FRONTIER_MODULE: &str = "sqlbuild._native"; -const FRONTIER_TYPES_MODULE: &str = "sqlbuild._native"; -const BUILD_INPUTS_MODULE: &str = "sqlbuild._native"; -const ASSEMBLE_MODULE: &str = "sqlbuild._native"; -const PROJECT_WITH_MODULE: &str = "sqlbuild._native"; -const PROMOTION_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use sqlbuild_core::constants::PROJECT_CONFIG_FILENAME; use sqlbuild_core::constants::TABLE_PROMOTION_MODE_SETTING_KEY; const LOCAL_CONFIG_FILENAME: &str = "sqlbuild_local.toml"; -const ANALYSIS_SELECTION_MODULE: &str = "sqlbuild._native"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; -const COLLECTION_RENDERING_MODULE: &str = "sqlbuild._native"; /// Optional keywords of the project graph builders. const GRAPH_OPTIONAL: [&str; 7] = [ "selected_target", @@ -63,7 +54,7 @@ pub(crate) fn lineage_mode<'py>(arguments: &KeywordArguments<'py>) -> PyResult, request: ProjectRequest) -> PyResult( let inference_profile: Bound<'py, PyAny> = adapter.call_method0("expression_inference_profile")?; let analysis_model_names: Bound<'py, PyAny> = call_with( - &imported( - py, - ANALYSIS_SELECTION_MODULE, - "resolve_analysis_model_names", - )?, + ®istry::native(py, NativeAttr::ResolveAnalysisModelNames)?, &[ ("compile_inputs", compile_inputs.clone()), ("inference_profile", inference_profile.clone()), @@ -177,7 +163,7 @@ fn python_compiled_project<'py>( ], )?; let project: Bound<'py, PyAny> = call_with( - &imported(py, ASSEMBLE_MODULE, "assemble_project")?, + ®istry::native(py, NativeAttr::AssembleProject)?, &[ ("inputs", compile_inputs.clone()), ("inference_profile", inference_profile), @@ -225,7 +211,7 @@ fn targeted_project<'py>( let project_config: Bound<'py, PyAny> = discovered.getattr("project_config")?; let local_config: Bound<'py, PyAny> = discovered.getattr("local_config")?; let adapter_name: Bound<'py, PyAny> = call_with( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", project_config), ("local_config", local_config.clone()), @@ -243,7 +229,7 @@ fn targeted_project<'py>( pyo3::types::PyString::new(py, PROJECT_CONFIG_FILENAME).into_any() }; let conflicts: Bound<'py, PyAny> = call_with( - &imported(py, PROMOTION_MODULE, "promotion_conflict_diagnostics")?, + ®istry::native(py, NativeAttr::PromotionConflictDiagnostics)?, &[ ("project", project.clone()), ( @@ -262,7 +248,7 @@ fn targeted_project<'py>( .collect::>>()?, )?; call_with( - &imported(py, PROJECT_WITH_MODULE, "project_with")?, + ®istry::native(py, NativeAttr::ProjectWith)?, &[ ("project", project), ("diagnostics", diagnostics.into_any()), @@ -279,18 +265,14 @@ fn frontier_compile_inputs<'py>( let discovered: &Bound<'py, PyAny> = request.discovered_inputs.bind(py); let adapter: &Bound<'py, PyAny> = request.adapter.bind(py); let collection_rendering: Bound<'py, PyAny> = call_with( - &imported( - py, - COLLECTION_RENDERING_MODULE, - "resolve_effective_collection_rendering", - )?, + ®istry::native(py, NativeAttr::ResolveEffectiveCollectionRendering)?, &[ ("project_config", discovered.getattr("project_config")?), ("declaration_override", py.None().into_bound(py)), ], )?; let adapter_context: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "CompileAdapterContext")?, + ®istry::class(py, RecordId::CompileAdapterContext)?, &[ ("value_renderer", adapter.clone()), ("collection_rendering", collection_rendering), @@ -302,7 +284,7 @@ fn frontier_compile_inputs<'py>( ], )?; let stage: Bound<'py, PyAny> = imported(py, "functools", "partial")?.call( - (imported(py, BUILD_INPUTS_MODULE, "build_compile_inputs")?,), + (registry::native(py, NativeAttr::BuildCompileInputs)?,), Some(&keywords( py, &[ @@ -325,12 +307,11 @@ fn frontier_compile_inputs<'py>( )?), )?; call_with( - &imported(py, FRONTIER_MODULE, "compile_frontier")?, + ®istry::native(py, NativeAttr::CompileFrontier)?, &[ ( "until", - imported(py, FRONTIER_TYPES_MODULE, "CompilerStage")? - .getattr("COMPILE_PROJECT_INPUTS")?, + registry::class(py, RecordId::CompilerStage)?.getattr("COMPILE_PROJECT_INPUTS")?, ), ("python_stage", stage), ], @@ -372,7 +353,7 @@ fn build_project_graph<'py>( (&["discovered_inputs", "adapter"], &optional), )?; let selection: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "CompileAnalysisSelection")?, + ®istry::class(py, RecordId::CompileAnalysisSelection)?, &[("no_cache", py_bool(py, arguments.flag("no_cache")?))], )?; selected_graph(&arguments, selection) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/python_plan_entries.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/python_plan_entries.rs index 0fa45a689e..387db3dbef 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/python_plan_entries.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/python_plan_entries.rs @@ -13,12 +13,11 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, keywords, }; use crate::bindings::_helpers::graph::python_node_graph::python_node_graph; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const PYTHON_NODE_TYPES_MODULE: &str = "sqlbuild.compiler.python_nodes.types"; -const REF_TYPES_MODULE: &str = "sqlbuild.python_nodes.types"; const SKIPPED_UPSTREAM_CODE: &str = "P501"; /// The Python node kinds and plan phases entries are built from. @@ -29,7 +28,7 @@ struct NodeKinds<'py> { impl<'py> NodeKinds<'py> { fn load(py: Python<'py>) -> PyResult { - let kinds: Bound<'py, PyAny> = imported(py, PYTHON_NODE_TYPES_MODULE, "PythonNodeKind")?; + let kinds: Bound<'py, PyAny> = host::entry(py, UserCodeHost::PythonNodeKind)?; Ok(Self { task: kinds.getattr("TASK")?, asset: kinds.getattr("ASSET")?, @@ -86,7 +85,7 @@ fn python_read_source_names_for<'py>( } let graph: Bound<'py, PyAny> = node_graph(&arguments.value("discovered_inputs")?)?; let source: Bound<'py, PyAny> = - imported(py, REF_TYPES_MODULE, "SqlResourceRefKind")?.getattr("SOURCE")?; + host::entry(py, UserCodeHost::SqlResourceRefKind)?.getattr("SOURCE")?; let mut names: Vec> = Vec::new(); for reference in call_with( &graph.getattr("selected_sql_refs")?, @@ -123,7 +122,7 @@ fn build_python_run_plan_outputs<'py>( &[], ), )?; - let outputs: Bound<'py, PyAny> = imported(py, NATIVE_RECORDS_MODULE, "PythonRunPlanOutputs")?; + let outputs: Bound<'py, PyAny> = registry::class(py, RecordId::PythonRunPlanOutputs)?; let plan_output: Bound<'py, PyAny> = arguments.value("plan_output")?; let run_selection: Bound<'py, PyAny> = arguments.value("run_selection")?; let selected: Bound<'py, PyAny> = arguments.value("selected_python_node_names")?; @@ -156,14 +155,14 @@ fn build_python_run_plan_outputs<'py>( )?), )?; let selection: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "PythonSqlRunSelection")?, + ®istry::class(py, RecordId::PythonSqlRunSelection)?, &[ ("sql_keys", plan_output.getattr("selected_keys")?), ("python_node_names", selected.clone()), ], )?; let lifecycle: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "build_python_sql_run_lifecycle")?, + ®istry::native(py, NativeAttr::BuildPythonSqlRunLifecycle)?, &[("selection", selection), ("python_graph", graph.clone())], )?; let entries: Bound<'py, PyTuple> = plan_entries( @@ -217,8 +216,8 @@ fn plan_entries<'py>( previous_identities: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = lifecycle.py(); - let phases: Bound<'py, PyAny> = imported(py, NATIVE_RECORDS_MODULE, "PythonRunPhase")?; - let entry_class: Bound<'py, PyAny> = imported(py, NATIVE_RECORDS_MODULE, "PythonPlanEntry")?; + let phases: Bound<'py, PyAny> = registry::class(py, RecordId::PythonRunPhase)?; + let entry_class: Bound<'py, PyAny> = registry::class(py, RecordId::PythonPlanEntry)?; let upstream: HashMap> = upstream_names(graph)?; let nodes: Bound<'py, PyAny> = graph.getattr("nodes_by_name")?; let mut entries: Vec> = Vec::new(); @@ -304,7 +303,7 @@ fn identity_status<'py>( previous_identities: &Bound<'py, PyAny>, ) -> PyResult> { let statuses: Bound<'py, PyAny> = - imported(identity.py(), NATIVE_RECORDS_MODULE, "PythonIdentityStatus")?; + registry::class(identity.py(), RecordId::PythonIdentityStatus)?; if identity.is_none() { return statuses.getattr("UNKNOWN"); } @@ -339,9 +338,9 @@ fn skipped_ingress_warnings<'py>( let nodes: Bound<'py, PyAny> = graph.getattr("nodes_by_name")?; let selected: Bound<'py, PyAny> = run_selection.getattr("python_node_names")?; let upstream: HashMap> = upstream_names(graph)?; - let warning_class: Bound<'py, PyAny> = imported(py, "sqlbuild._native", "PlanWarning")?; + let warning_class: Bound<'py, PyAny> = registry::class(py, RecordId::PlanWarning)?; let severity: Bound<'py, PyAny> = - imported(py, "sqlbuild._native", "WarningSeverity")?.getattr("WARNING")?; + registry::class(py, RecordId::WarningSeverity)?.getattr("WARNING")?; let mut warnings: Vec> = Vec::new(); for loader in loaders { if !nodes.contains(loader.as_str())? { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/relation_targets.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/relation_targets.rs index b534c3e314..ff9880d2d4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/relation_targets.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/relation_targets.rs @@ -9,12 +9,11 @@ use pyo3::{pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, }; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; use crate::bindings::_helpers::references::source_relations::render_source_relation; -const REF_MODELS_MODULE: &str = "sqlbuild.python_nodes.models"; -const REF_TYPES_MODULE: &str = "sqlbuild.python_nodes.types"; -const QUALIFIED_NAME_MODULE: &str = - "sqlbuild.adapter.relations.main.resolve_relation_location_qualified_name"; const SHARED_EXCEPTIONS_MODULE: &str = "sqlbuild.errors.contracts.exceptions"; /// The `SqlResourceRefKind` members a relation map distinguishes. @@ -26,7 +25,7 @@ struct RefKinds<'py> { impl<'py> RefKinds<'py> { fn load(py: Python<'py>) -> PyResult { - let kinds: Bound<'py, PyAny> = imported(py, REF_TYPES_MODULE, "SqlResourceRefKind")?; + let kinds: Bound<'py, PyAny> = host::entry(py, UserCodeHost::SqlResourceRefKind)?; Ok(Self { model: kinds.getattr("MODEL")?, seed: kinds.getattr("SEED")?, @@ -118,7 +117,7 @@ fn planned_relation_refs<'py>( kinds: &RefKinds<'py>, ) -> PyResult> { let py: Python<'py> = plan_output.py(); - let reference: Bound<'py, PyAny> = imported(py, REF_MODELS_MODULE, "SqlResourceRef")?; + let reference: Bound<'py, PyAny> = host::entry(py, UserCodeHost::SqlResourceRef)?; let refs: Bound<'py, PySet> = PySet::empty(py)?; for (attribute, kind) in [ ("model_locations", &kinds.model), @@ -189,10 +188,9 @@ fn qualified_name<'py>( location: &Bound<'py, PyAny>, ) -> PyResult> { call_with( - &imported( + &host::entry( adapter.py(), - QUALIFIED_NAME_MODULE, - "resolve_relation_location_qualified_name", + AdapterHost::ResolveRelationLocationQualifiedName, )?, &[("adapter", adapter.clone()), ("location", location.clone())], ) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/selection_phases.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/selection_phases.rs index 9ab6883540..65b0c4e43c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/selection_phases.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/selection_phases.rs @@ -6,14 +6,9 @@ use pyo3::{pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::boundary::keyword_request::Request; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const SPLIT_SELECTORS_MODULE: &str = "sqlbuild._native"; -const PLANNER_SCOPES_MODULE: &str = "sqlbuild._native"; -const STATIC_SCOPE_MODULE: &str = "sqlbuild._native"; -const STATIC_RELATIONS_MODULE: &str = "sqlbuild._native"; const DATACLASSES_MODULE: &str = "dataclasses"; /// Whether a selection names nothing to select or exclude. @@ -28,7 +23,7 @@ fn unit_test_selectors_applied<'py>( ) -> PyResult> { let py: Python<'py> = discovered.py(); let resolved: Bound<'py, PyAny> = if options.is_none() { - imported(py, NATIVE_RECORDS_MODULE, "CompilePipelineOptions")?.call0()? + registry::class(py, RecordId::CompilePipelineOptions)?.call0()? } else { options.clone() }; @@ -36,11 +31,7 @@ fn unit_test_selectors_applied<'py>( return Ok(resolved); } let split: Bound<'py, PyAny> = call_with( - &imported( - py, - SPLIT_SELECTORS_MODULE, - "split_project_unit_test_selectors", - )?, + ®istry::native(py, NativeAttr::SplitProjectUnitTestSelectors)?, &[ ("select", resolved.getattr("select")?), ("exclude", resolved.getattr("exclude")?), @@ -71,13 +62,13 @@ fn planner_scopes<'py>( ) -> PyResult> { let py: Python<'py> = project.py(); call_with( - &imported(py, PLANNER_SCOPES_MODULE, "resolve_planner_scopes")?, + ®istry::native(py, NativeAttr::ResolvePlannerScopes)?, &[ ("project", project.clone()), ( "selection", call_with( - &imported(py, NATIVE_RECORDS_MODULE, "PlannerSelection")?, + ®istry::class(py, RecordId::PlannerSelection)?, &[ ("select", selection.getattr("select")?), ("exclude", selection.getattr("exclude")?), @@ -87,7 +78,7 @@ fn planner_scopes<'py>( ( "policies", call_with( - &imported(py, NATIVE_RECORDS_MODULE, "PlannerPolicies")?, + ®istry::class(py, RecordId::PlannerPolicies)?, &[("auto_load_sources", selection.getattr("auto_load_sources")?)], )?, ), @@ -129,7 +120,7 @@ fn resolve_configured_rule_selection<'py>( return Ok(PyFrozenSet::empty(py)?.into_any()); } let selection: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "resolve_compile_analysis_selection")?, + ®istry::native(py, NativeAttr::ResolveCompileAnalysisSelection)?, &[ ("options", options.clone()), ("discovered_inputs", discovered_inputs.clone()), @@ -160,7 +151,7 @@ fn resolve_analysis_model_names<'py>( return Ok(py.None().into_bound(py)); } let structural_project: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "assemble_project")?, + ®istry::native(py, NativeAttr::AssembleProject)?, &[ ("inputs", compile_inputs.clone()), ("inference_profile", inference_profile.clone()), @@ -188,7 +179,7 @@ fn deferred_locations<'py>( return Ok(py.None().into_bound(py)); } let deferred_target: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "resolve_deferred_target_config")?, + ®istry::native(py, NativeAttr::ResolveDeferredTargetConfig)?, &[ ("discovered_inputs", discovered.clone()), ("defer_to", defer_to), @@ -199,7 +190,7 @@ fn deferred_locations<'py>( ], )?; call_with( - &imported(py, NATIVE_MODULE, "build_deferred_locations")?, + ®istry::native(py, NativeAttr::BuildDeferredLocations)?, &[ ("project", project.clone()), ("deferred_target_config", deferred_target), @@ -241,7 +232,7 @@ fn compile_static_command_context<'py>( selected_keys = PyFrozenSet::empty(py)?.into_any(); } let compiled: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "compile_project_phase")?, + ®istry::native(py, NativeAttr::CompileProjectPhase)?, &[ ("discovered_inputs", discovered.clone()), ("adapter", adapter.clone()), @@ -256,13 +247,13 @@ fn compile_static_command_context<'py>( let deferred: Bound<'py, PyAny> = deferred_locations((&discovered, &adapter), &options, &project)?; let scope: Bound<'py, PyAny> = call_with( - &imported(py, STATIC_SCOPE_MODULE, "resolve_static_command_scope")?, + ®istry::native(py, NativeAttr::ResolveStaticCommandScope)?, &[ ("project", project.clone()), ( "selection", call_with( - &imported(py, NATIVE_RECORDS_MODULE, "PlannerSelection")?, + ®istry::class(py, RecordId::PlannerSelection)?, &[ ("select", options.getattr("select")?), ("exclude", options.getattr("exclude")?), @@ -275,11 +266,7 @@ fn compile_static_command_context<'py>( ], )?; let relations: Bound<'py, PyAny> = call_with( - &imported( - py, - STATIC_RELATIONS_MODULE, - "resolve_static_relation_context", - )?, + ®istry::native(py, NativeAttr::ResolveStaticRelationContext)?, &[ ("project", project.clone()), ("adapter", adapter), @@ -287,7 +274,7 @@ fn compile_static_command_context<'py>( ( "deferral", call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DeferralInputs")?, + ®istry::class(py, RecordId::DeferralInputs)?, &[ ("deferred_locations", deferred), ("defer_sources_to", options.getattr("defer_sources_to")?), @@ -307,7 +294,7 @@ fn compile_static_command_context<'py>( ], )?; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "StaticCommandContext")?, + ®istry::class(py, RecordId::StaticCommandContext)?, &[ ("project", project), ("scope", scope), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/target_defaults.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/target_defaults.rs index 18b224e71d..27a3ec9e39 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/target_defaults.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/target_defaults.rs @@ -4,9 +4,8 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::PyTuple; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; /// The adapter defaults every compiled location falls back to. pub(crate) struct TargetDefaults<'py> { @@ -41,8 +40,8 @@ pub(crate) fn apply_target_defaults<'py>( defaults: &TargetDefaults<'py>, ) -> PyResult> { let py: Python<'py> = project.py(); - let compiled_model: Bound<'py, PyAny> = imported(py, COMPILE_MODELS_MODULE, "CompiledModel")?; - let native_seed: Bound<'py, PyAny> = imported(py, NATIVE_MODULE, "NativeCompiledSeed")?; + let compiled_model: Bound<'py, PyAny> = registry::class(py, RecordId::CompiledModel)?; + let native_seed: Bound<'py, PyAny> = registry::native(py, NativeAttr::NativeCompiledSeed)?; let replace: Bound<'py, PyAny> = imported(py, "dataclasses", "replace")?; let mut models: Vec> = Vec::new(); for model in project.getattr("models")?.try_iter()? { @@ -98,7 +97,7 @@ pub(crate) fn apply_target_defaults<'py>( )?); } call_with( - &imported(py, NATIVE_MODULE, "project_with")?, + ®istry::native(py, NativeAttr::ProjectWith)?, &[ ("project", project.clone()), ( @@ -156,7 +155,7 @@ fn resolved_target<'py>( return Ok(target.clone()); } call_with( - &imported(py, COMPILE_MODELS_MODULE, "CompiledRelationLocation")?, + ®istry::class(py, RecordId::CompiledRelationLocation)?, &[ ("database", database), ("schema", schema), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/targets.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/targets.rs index 3713daa5ec..3bdd7d99b4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/targets.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/targets.rs @@ -21,16 +21,10 @@ use sqlbuild_core::text::main::python_strip::python_strip; use sqlbuild_model_config::templates::models::TemplateOptions; use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, keywords, + KeywordArguments, call_with, keywords, }; use crate::bindings::_helpers::model_config::template_data::expanded_template_data; -const PLANNER_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; -const TARGET_NAME_MODULE: &str = "sqlbuild._native"; -const TARGET_CONFIG_MODULE: &str = "sqlbuild._native"; -const EFFECTIVE_RUNTIME_MODULE: &str = "sqlbuild._native"; -const EFFECTIVE_CONFIG_MODULE: &str = "sqlbuild._native"; -const SETTING_HELP_MODULE: &str = "sqlbuild._native"; /// Connection settings that identify the warehouse two targets write to. const CONNECTION_IDENTITY_KEYS: [&str; 7] = [ "account", @@ -45,6 +39,8 @@ const CONNECTION_IDENTITY_KEYS: [&str; 7] = [ const EXPLICIT_TARGET_ADAPTERS: [&str; 3] = ["snowflake", "bigquery", "databricks"]; const LOADER_NAMESPACE_LABEL: &str = "managed loader target namespace"; const DEFAULTS_HELP_PURPOSE: &str = "to set project-wide defaults"; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr}; +use crate::bindings::_helpers::records::registry; use sqlbuild_core::constants::PROJECT_CONFIG_FILENAME; const DEFAULTS_SECTION: &str = "defaults"; @@ -56,7 +52,7 @@ type TargetNamespaces = (Vec<(String, String)>, Vec); /// A `PlannerInputError` with `message`, `code` and optional `help`. fn planner_error(py: Python<'_>, message: String, code: &str, help: Option) -> PyErr { let raised: PyResult> = (|| { - imported(py, PLANNER_EXCEPTIONS_MODULE, "PlannerInputError")?.call( + registry::error(py, ErrorId::PlannerInput)?.call( (message,), Some(&keywords( py, @@ -131,7 +127,7 @@ pub(crate) fn named_target_schema_strategy( let project_config: Bound<'_, PyAny> = discovered.getattr("project_config")?; let local_config: Bound<'_, PyAny> = discovered.getattr("local_config")?; let target_name: Bound<'_, PyAny> = call_with( - &imported(py, TARGET_NAME_MODULE, "resolve_target_name")?, + ®istry::native(py, NativeAttr::ResolveTargetName)?, &[ ("project_config", project_config.clone()), ("local_config", local_config.clone()), @@ -142,7 +138,7 @@ pub(crate) fn named_target_schema_strategy( return Ok(()); } let target_config: Bound<'_, PyAny> = call_with( - &imported(py, TARGET_CONFIG_MODULE, "resolve_target_config")?, + ®istry::native(py, NativeAttr::ResolveTargetConfig)?, &[ ("project_config", project_config), ("local_config", local_config), @@ -266,7 +262,7 @@ fn target_namespaces( let py: Python<'_> = discovered.py(); let name: Bound<'_, PyAny> = PyString::new(py, target_name).into_any(); let target_config: Bound<'_, PyAny> = call_with( - &imported(py, TARGET_CONFIG_MODULE, "resolve_target_config")?, + ®istry::native(py, NativeAttr::ResolveTargetConfig)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), @@ -274,11 +270,7 @@ fn target_namespaces( ], )?; let runtime: Bound<'_, PyAny> = call_with( - &imported( - py, - EFFECTIVE_RUNTIME_MODULE, - "build_effective_runtime_config", - )?, + ®istry::native(py, NativeAttr::BuildEffectiveRuntimeConfig)?, &[ ("discovered_inputs", discovered.clone()), ("selected_target", name.clone()), @@ -288,11 +280,7 @@ fn target_namespaces( runtime.extract()?; let connection: Bound<'_, PyAny> = if resolved_connection.is_none() { call_with( - &imported( - py, - EFFECTIVE_CONFIG_MODULE, - "build_effective_connection_config", - )?, + ®istry::native(py, NativeAttr::BuildEffectiveConnectionConfig)?, &[ ("discovered_inputs", discovered.clone()), ("selected_target", name), @@ -460,7 +448,7 @@ fn required_target_parts( return Ok(()); }; let help: String = call_with( - &imported(py, SETTING_HELP_MODULE, "setting_help")?, + ®istry::native(py, NativeAttr::SettingHelp)?, &[ ( "purpose", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_outputs/runtime_target.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_outputs/runtime_target.rs index 8a6a95628b..4e8502b286 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_outputs/runtime_target.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_outputs/runtime_target.rs @@ -16,8 +16,9 @@ use sqlbuild_core::text::main::python_isspace::python_isspace; use crate::bindings::_helpers::analysis::sql_test_comparisons::single_comparison_sql; use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; -const LIFECYCLE_TYPES: &str = "sqlbuild.adapter.contract.types"; const RUN_DIR: &str = "run"; const TESTS_DIR: &str = "tests"; const CHECKS_DIR: &str = "checks"; @@ -97,7 +98,7 @@ fn write_text(path: &Bound<'_, PyAny>, text: &str) -> PyResult<()> { fn sql_statements(result: &Bound<'_, PyAny>) -> PyResult> { let sql_kind: Bound<'_, PyAny> = - imported(result.py(), LIFECYCLE_TYPES, "LifeCycleEventKind")?.getattr("SQL")?; + host::entry(result.py(), AdapterHost::LifeCycleEventKind)?.getattr("SQL")?; let mut statements: Vec = Vec::new(); for event in result.getattr("lifecycle_events")?.try_iter()? { let event: Bound<'_, PyAny> = event?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_records/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_records/constants.rs index f47fb226b8..2e17eb2a39 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_records/constants.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_records/constants.rs @@ -1,5 +1,7 @@ //! Layouts of the compile and discovery value records hosted natively. +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::records::models::{FieldDefault, FieldSpec, RecordSpec}; const R_RELATED_LOCATION: RecordSpec = RecordSpec { @@ -52,11 +54,11 @@ const R_COMPILE_MODEL_CONFIG: RecordSpec = RecordSpec { FieldSpec::new("logical_database", FieldDefault::None), FieldSpec::new( "time_travel_retention", - FieldDefault::Call("sqlbuild._native", "ResolvedTimeTravelRetention"), + FieldDefault::NativeCall(RecordId::ResolvedTimeTravelRetention), ), FieldSpec::new( "table_type", - FieldDefault::Call("sqlbuild._native", "ResolvedTableType"), + FieldDefault::NativeCall(RecordId::ResolvedTableType), ), ], }; @@ -181,11 +183,11 @@ const R_COMPILED_LINEAGE_COLUMN_FACT: RecordSpec = RecordSpec { FieldSpec::new("upstream_columns", FieldDefault::EmptyTuple), FieldSpec::new( "transform_kind", - FieldDefault::Member("sqlbuild._native", "ColumnTransformKind", "UNKNOWN"), + FieldDefault::NativeMember(RecordId::ColumnTransformKind, "UNKNOWN"), ), FieldSpec::new( "confidence", - FieldDefault::Member("sqlbuild._native", "ColumnLineageConfidence", "UNKNOWN"), + FieldDefault::NativeMember(RecordId::ColumnLineageConfidence, "UNKNOWN"), ), ], }; @@ -299,7 +301,7 @@ const R_COMPILE_PROJECT_INPUTS: RecordSpec = RecordSpec { FieldSpec::new("effective_connection", FieldDefault::EmptyDict), FieldSpec::new( "effective_settings", - FieldDefault::Call("sqlbuild._native", "SettingsConfig"), + FieldDefault::NativeCall(RecordId::SettingsConfig), ), FieldSpec::new("no_sql_validation", FieldDefault::False), FieldSpec::new("effective_vars", FieldDefault::EmptyDict), @@ -323,7 +325,7 @@ const R_COMPILE_PROJECT_INPUTS: RecordSpec = RecordSpec { FieldSpec::new("external_sql_reference_resolver", FieldDefault::None), FieldSpec::new( "scope_index", - FieldDefault::Call("sqlbuild._native", "ScopeIndex"), + FieldDefault::NativeCall(RecordId::ScopeIndex), ), FieldSpec { name: "declaration_scope", @@ -448,7 +450,7 @@ const R_COMPILE_SQL_TEST_INPUT: RecordSpec = RecordSpec { ), FieldSpec::new( "payload", - FieldDefault::Call("sqlbuild._native", "CompileModelSqlTestInputPayload"), + FieldDefault::NativeCall(RecordId::CompileModelSqlTestInputPayload), ), FieldSpec::new("declaration_usages", FieldDefault::EmptyTuple), FieldSpec::new("parent_name", FieldDefault::None), @@ -599,7 +601,7 @@ const R_DISCOVERED_SQL_HOOK_FILE: RecordSpec = RecordSpec { FieldSpec::new("description", FieldDefault::None), FieldSpec::new( "scope_kind", - FieldDefault::Member("sqlbuild._native", "ScopeKind", "GLOBAL"), + FieldDefault::NativeMember(RecordId::ScopeKind, "GLOBAL"), ), FieldSpec::new("ownership_root", FieldDefault::None), FieldSpec::new("owning_path", FieldDefault::None), @@ -621,7 +623,7 @@ const R_DISCOVERED_ENUM_FILE: RecordSpec = RecordSpec { FieldSpec::required("declarations"), FieldSpec::new( "scope_kind", - FieldDefault::Member("sqlbuild._native", "ScopeKind", "GLOBAL"), + FieldDefault::NativeMember(RecordId::ScopeKind, "GLOBAL"), ), FieldSpec::new("ownership_root", FieldDefault::None), FieldSpec::new("owning_path", FieldDefault::None), @@ -638,7 +640,7 @@ const R_DISCOVERED_CONSTANT_FILE: RecordSpec = RecordSpec { FieldSpec::required("declarations"), FieldSpec::new( "scope_kind", - FieldDefault::Member("sqlbuild._native", "ScopeKind", "GLOBAL"), + FieldDefault::NativeMember(RecordId::ScopeKind, "GLOBAL"), ), FieldSpec::new("ownership_root", FieldDefault::None), FieldSpec::new("owning_path", FieldDefault::None), @@ -666,7 +668,7 @@ const R_DISCOVERED_MODEL_SCHEMA_FILE: RecordSpec = RecordSpec { FieldSpec::required("declarations"), FieldSpec::new( "scope_kind", - FieldDefault::Member("sqlbuild._native", "ScopeKind", "GLOBAL"), + FieldDefault::NativeMember(RecordId::ScopeKind, "GLOBAL"), ), FieldSpec::new("ownership_root", FieldDefault::None), FieldSpec::new("owning_path", FieldDefault::None), @@ -742,11 +744,11 @@ const R_DISCOVERED_AUDIT_FILE: RecordSpec = RecordSpec { FieldSpec::required("blocks"), FieldSpec::new( "declaration_kind", - FieldDefault::Member("sqlbuild._native", "DeclarationKind", "AUDIT"), + FieldDefault::NativeMember(RecordId::DeclarationKind, "AUDIT"), ), FieldSpec::new( "scope_kind", - FieldDefault::Member("sqlbuild._native", "ScopeKind", "GLOBAL"), + FieldDefault::NativeMember(RecordId::ScopeKind, "GLOBAL"), ), FieldSpec::new("ownership_root", FieldDefault::None), FieldSpec::new("owning_path", FieldDefault::None), @@ -897,7 +899,7 @@ const R_DISCOVERED_MACRO_FILE: RecordSpec = RecordSpec { FieldSpec::required("contents"), FieldSpec::new( "scope_kind", - FieldDefault::Member("sqlbuild._native", "ScopeKind", "GLOBAL"), + FieldDefault::NativeMember(RecordId::ScopeKind, "GLOBAL"), ), FieldSpec::new("ownership_root", FieldDefault::None), FieldSpec::new("owning_path", FieldDefault::None), @@ -942,7 +944,7 @@ const R_DISCOVERED_LOADER_FUNCTION: RecordSpec = RecordSpec { FieldSpec::new("description", FieldDefault::None), FieldSpec::new( "connection_mode", - FieldDefault::Member("sqlbuild._native", "LoaderConnectionMode", "SQLBUILD"), + FieldDefault::NativeMember(RecordId::LoaderConnectionMode, "SQLBUILD"), ), FieldSpec::new("provider_usages", FieldDefault::EmptyTuple), ], @@ -994,11 +996,7 @@ const R_DISCOVERED_CHECK_FUNCTION: RecordSpec = RecordSpec { FieldSpec::required("depends_on"), FieldSpec::new( "severity", - FieldDefault::Member( - "sqlbuild.python_nodes.types", - "PythonCheckSeverity", - "ERROR", - ), + FieldDefault::HostMember(UserCodeHost::PythonCheckSeverity.id(), "ERROR"), ), FieldSpec::new("tags", FieldDefault::EmptyTuple), FieldSpec::new("group", FieldDefault::None), @@ -1020,7 +1018,7 @@ const R_DISCOVERED_HOOK_FUNCTION: RecordSpec = RecordSpec { FieldSpec::new("provider_usages", FieldDefault::EmptyTuple), FieldSpec::new( "scope_kind", - FieldDefault::Member("sqlbuild._native", "ScopeKind", "GLOBAL"), + FieldDefault::NativeMember(RecordId::ScopeKind, "GLOBAL"), ), FieldSpec::new("ownership_root", FieldDefault::None), FieldSpec::new("owning_path", FieldDefault::None), @@ -1074,7 +1072,7 @@ const R_DISCOVERED_EVENT_EXPORTER: RecordSpec = RecordSpec { ), FieldSpec::new( "min_severity", - FieldDefault::Member("sqlbuild._native", "EventExportSeverity", "DEBUG"), + FieldDefault::NativeMember(RecordId::EventExportSeverity, "DEBUG"), ), FieldSpec::new("provider_usages", FieldDefault::EmptyTuple), ], @@ -1269,7 +1267,7 @@ const R_MACRO_CONTEXT: RecordSpec = RecordSpec { }, FieldSpec { name: "_collection_rendering", - default: FieldDefault::Member("sqlbuild._native", "CollectionRendering", "VALUE_LIST"), + default: FieldDefault::NativeMember(RecordId::CollectionRendering, "VALUE_LIST"), compared: false, shown: false, }, @@ -1368,7 +1366,7 @@ const R_COMPILE_MODEL_INPUT: RecordSpec = RecordSpec { FieldSpec::required("model_file"), FieldSpec::new( "config", - FieldDefault::Call("sqlbuild._native", "CompileModelConfig"), + FieldDefault::NativeCall(RecordId::CompileModelConfig), ), FieldSpec::new("query_sql", FieldDefault::Text("")), FieldSpec::new("macro_source_sql", FieldDefault::Text("")), @@ -1518,16 +1516,16 @@ const R_COMPILED_PROJECT: RecordSpec = RecordSpec { FieldSpec::new("sql_analysis_dialect", FieldDefault::None), FieldSpec::new( "sql_lexical_syntax", - FieldDefault::Call("sqlbuild._native", "SqlLexicalSyntax"), + FieldDefault::NativeCall(RecordId::SqlLexicalSyntax), ), FieldSpec::new("compile_cache_dir", FieldDefault::None), FieldSpec::new( "settings", - FieldDefault::Call("sqlbuild._native", "SettingsConfig"), + FieldDefault::NativeCall(RecordId::SettingsConfig), ), FieldSpec::new( "scenario", - FieldDefault::Call("sqlbuild._native", "ScenarioConfig"), + FieldDefault::NativeCall(RecordId::ScenarioConfig), ), FieldSpec::new("models", FieldDefault::EmptyTuple), FieldSpec::new("sources", FieldDefault::EmptyTuple), @@ -1549,7 +1547,7 @@ const R_COMPILED_PROJECT: RecordSpec = RecordSpec { FieldSpec::new("external_sql_reference_resolver", FieldDefault::None), FieldSpec::new( "scope_index", - FieldDefault::Call("sqlbuild._native", "ScopeIndex"), + FieldDefault::NativeCall(RecordId::ScopeIndex), ), FieldSpec { name: "sql_expansions", @@ -1587,7 +1585,7 @@ const R_COMPILED_SQL_TEST: RecordSpec = RecordSpec { ), FieldSpec::new( "payload", - FieldDefault::Call("sqlbuild._native", "CompiledModelSqlTestPayload"), + FieldDefault::NativeCall(RecordId::CompiledModelSqlTestPayload), ), FieldSpec::new("source_path", FieldDefault::None), FieldSpec::new("ownership_root", FieldDefault::None), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/attachment.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/attachment.rs index ec2abe5bcb..c1da8fb1c9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/attachment.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/attachment.rs @@ -1,6 +1,5 @@ //! Seeds, functions, sources, SQL tests, scenarios and audits attached from discovered inputs. -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class_at; use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyCFunction, PyDict, PyDictMethods, PyTuple}; use pyo3::{IntoPyObjectExt, pyfunction, wrap_pyfunction}; @@ -23,22 +22,16 @@ use crate::bindings::_helpers::compiled_project::source_loop::SourceRow; use crate::bindings::_helpers::compiled_project::sql_test_loop::{ TestBlockRow, TestCaseRow, TestFileRow, }; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sql_test_glue::constants::SQL_TEST_CTE_SCAN_ALGORITHM; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_RECORDS: &str = "sqlbuild._native"; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; -const SCOPE_TYPES_MODULE: &str = "sqlbuild._native"; -const SCOPE_MODELS_MODULE: &str = "sqlbuild._native"; const AUDITING_TYPES_MODULE: &str = "sqlbuild.compiler.auditing.types"; -const CONTRACT_MODELS_MODULE: &str = "sqlbuild._native"; -const SQL_VALUE_ERRORS_MODULE: &str = "sqlbuild.sql_values.exceptions"; const YAML_FILE_SUFFIXES: [&str; 2] = [".yml", ".yaml"]; -const PYTHON_RESOURCES_MODULE: &str = - "sqlbuild.compiler.compile._helpers.diagnostics.python_resources"; const PATHLIB_MODULE: &str = "pathlib"; const TEST_TIMING_KEY: &str = "test_input_compile_ms"; const DESCRIPTION_KEY: &str = "description"; @@ -73,8 +66,17 @@ fn enum_member<'py>( /// `{member.value: member for member in enum_class}`. fn by_value<'py>(py: Python<'py>, module: &str, class: &str) -> PyResult> { - let members: Bound<'py, PyDict> = PyDict::new(py); - for member in imported(py, module, class)?.try_iter()? { + members_by_value(&imported(py, module, class)?) +} + +/// `{member.value: member for member in enum_class}` of a native string enum. +fn native_by_value(py: Python<'_>, class: RecordId) -> PyResult> { + members_by_value(®istry::class(py, class)?) +} + +fn members_by_value<'py>(enum_class: &Bound<'py, PyAny>) -> PyResult> { + let members: Bound<'py, PyDict> = PyDict::new(enum_class.py()); + for member in enum_class.try_iter()? { let member: Bound<'py, PyAny> = member?; members.set_item(member.getattr("value")?, member)?; } @@ -180,13 +182,13 @@ pub(crate) fn attach_resources<'py>( .into_iter() .map(Bound::unbind) .collect(), - &imported(py, COMPILE_ERRORS_MODULE, "CompileInputError")?, + ®istry::error(py, ErrorId::CompileInput)?, )?; let defaults: Bound<'py, PyAny> = discovered_inputs .getattr("project_config")? .getattr("defaults")?; let function_kind: Bound<'py, PyAny> = - enum_member(py, (SCOPE_TYPES_MODULE, "ResourceKind", "FUNCTION"))?; + registry::member(py, RecordId::ResourceKind, "FUNCTION")?; let functions = NativeCompiledProject::attach_functions( native_project, ( @@ -226,7 +228,7 @@ pub(crate) fn attach_resources<'py>( ( attr(&defaults, "contract")?, validate, - enum_member(py, (SCOPE_TYPES_MODULE, "ResourceKind", "SOURCE"))?.unbind(), + registry::member(py, RecordId::ResourceKind, "SOURCE")?.unbind(), ), )?; PyTuple::new( @@ -300,8 +302,8 @@ fn parameter_renderer<'py>(value_renderer: &Bound<'py, PyAny>) -> PyResult = PyTuple::new( py, [ - imported(py, SQL_VALUE_ERRORS_MODULE, "SqlValueRenderingError")?, - imported(py, NATIVE_RECORDS, "SqlValueValidationError")?, + host::entry(py, ExecutorHost::SqlValueRenderingError)?, + registry::native(py, NativeAttr::SqlValueValidationError)?, ], )? .into_any() @@ -336,33 +338,36 @@ fn sql_test_settings<'py>( ) -> PyResult> { let settings: Bound<'py, PyDict> = PyDict::new(py); for (key, class) in [ - ("test_input", "CompileSqlTestInput"), - ("model_payload", "CompileModelSqlTestInputPayload"), - ("direct_payload", "CompileDirectLogicSqlTestInputPayload"), - ("test_cte", "CompileSqlTestCte"), - ("scenario_input", "CompileSqlScenarioInput"), - ("scenario_cte", "CompileSqlScenarioCte"), + ("test_input", RecordId::CompileSqlTestInput), + ("model_payload", RecordId::CompileModelSqlTestInputPayload), + ( + "direct_payload", + RecordId::CompileDirectLogicSqlTestInputPayload, + ), + ("test_cte", RecordId::CompileSqlTestCte), + ("scenario_input", RecordId::CompileSqlScenarioInput), + ("scenario_cte", RecordId::CompileSqlScenarioCte), ] { - settings.set_item(key, shape_class_at(py, COMPILE_MODELS_MODULE, class)?)?; + settings.set_item(key, registry::class(py, class)?)?; } settings.set_item("modes", by_value(py, COMPILE_TYPES_MODULE, "SqlTestMode")?)?; settings.set_item( "test_kind", - enum_member(py, (SCOPE_TYPES_MODULE, "ResourceKind", "TEST"))?, + registry::member(py, RecordId::ResourceKind, "TEST")?, )?; settings.set_item( "scenario_kind", - enum_member(py, (SCOPE_TYPES_MODULE, "ResourceKind", "SCENARIO"))?, + registry::member(py, RecordId::ResourceKind, "SCENARIO")?, )?; settings.set_item( "runtime_usage", - enum_member(py, (SCOPE_TYPES_MODULE, "UsageKind", "RUNTIME"))?, + registry::member(py, RecordId::UsageKind, "RUNTIME")?, )?; settings.set_item("render_parameter", parameter_renderer(value_renderer)?)?; settings.set_item("adapter_name", value_renderer.getattr("adapter_name")?)?; settings.set_item( "source_location", - imported(py, CONTRACT_MODELS_MODULE, "SourceLocation")?, + registry::class(py, RecordId::SourceLocation)?, )?; settings.set_item("scan_store", scan_cache.call_method0("opened")?)?; settings.set_item( @@ -394,8 +399,7 @@ fn audit_block_row(block: Bound<'_, PyAny>) -> PyResult { fn audit_file_row(file: Bound<'_, PyAny>) -> PyResult { let py: Python<'_> = file.py(); - let generic: Bound<'_, PyAny> = - enum_member(py, (SCOPE_TYPES_MODULE, "DeclarationKind", "AUDIT"))?; + let generic: Bound<'_, PyAny> = registry::member(py, RecordId::DeclarationKind, "AUDIT")?; Ok(( text(&file, "file_path")?, text(&file, "relative_path")?, @@ -419,7 +423,7 @@ fn builtin_audit_file<'py>( let relative_path: Bound<'py, PyAny> = path_class.call1((format!("audits/generic/{name}.sql"),))?; let block: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DiscoveredAuditBlock")?, + ®istry::class(py, RecordId::DiscoveredAuditBlock)?, &[ ("audit_index", 0.into_bound_py_any(py)?), ("header_values", PyDict::new(py).into_any()), @@ -427,7 +431,7 @@ fn builtin_audit_file<'py>( ], )?; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DiscoveredAuditFile")?, + ®istry::class(py, RecordId::DiscoveredAuditFile)?, &[ ( "file_path", @@ -468,22 +472,19 @@ fn audit_settings<'py>( settings.set_item("builtins", builtins)?; settings.set_item( "declaration_identity", - imported(py, SCOPE_MODELS_MODULE, "DeclarationIdentity")?, + registry::class(py, RecordId::DeclarationIdentity)?, )?; settings.set_item( "singular_kind", - enum_member( - py, - (SCOPE_TYPES_MODULE, "DeclarationKind", "SINGULAR_AUDIT"), - )?, + registry::member(py, RecordId::DeclarationKind, "SINGULAR_AUDIT")?, )?; settings.set_item( "audit_kind", - enum_member(py, (SCOPE_TYPES_MODULE, "DeclarationKind", "AUDIT"))?, + registry::member(py, RecordId::DeclarationKind, "AUDIT")?, )?; settings.set_item( "resource_kinds", - by_value(py, SCOPE_TYPES_MODULE, "ResourceKind")?, + native_by_value(py, RecordId::ResourceKind)?, )?; Ok(settings) } @@ -518,15 +519,12 @@ fn diagnostic_settings<'py>( let settings: Bound<'py, PyDict> = PyDict::new(py); settings.set_item( "compiler_diagnostic", - imported(py, COMPILE_MODELS_MODULE, "CompilerDiagnostic")?, - )?; - settings.set_item( - "compile_error", - imported(py, COMPILE_ERRORS_MODULE, "CompileInputError")?, + registry::class(py, RecordId::CompilerDiagnostic)?, )?; + settings.set_item("compile_error", registry::error(py, ErrorId::CompileInput)?)?; settings.set_item( "source_location", - imported(py, CONTRACT_MODELS_MODULE, "SourceLocation")?, + registry::class(py, RecordId::SourceLocation)?, )?; settings.set_item("path_class", imported(py, PATHLIB_MODULE, "Path")?)?; settings.set_item("model_files", items(discovered, "model_files")?)?; @@ -577,7 +575,7 @@ fn diagnostic_settings<'py>( settings.set_item( "python_resources", call_with( - &imported(py, PYTHON_RESOURCES_MODULE, "python_resource_rows")?, + &host::entry(py, UserCodeHost::PythonResourceRows)?, &[ ("discovered_inputs", discovered.clone()), ("source_inputs", source_inputs.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/expanded_offsets.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/expanded_offsets.rs index e626aedc67..e784fe5ac3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/expanded_offsets.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/expanded_offsets.rs @@ -1,13 +1,13 @@ //! Map offsets in expanded SQL back onto the authored text that produced them. +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::PyDict; use pyo3::{pyfunction, wrap_pyfunction}; use sqlbuild_analysis::compiled_project::main::map_expanded_offset::map_expanded_offset as mapped; use sqlbuild_analysis::compiled_project::models::SplicedSpan; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - /// `map_expanded_offset(*, offset, passes)`: a `MappedOffset` in the first pass's source. #[pyfunction] #[pyo3(signature = (*, offset, passes))] @@ -34,9 +34,7 @@ fn map_expanded_offset<'py>( let arguments: Bound<'py, PyDict> = PyDict::new(py); arguments.set_item("offset", offset)?; arguments.set_item("generated", generated)?; - py.import(NATIVE_RECORDS_MODULE)? - .getattr("MappedOffset")? - .call((), Some(&arguments)) + registry::class(py, RecordId::MappedOffset)?.call((), Some(&arguments)) } pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/loop_classes.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/loop_classes.rs index 083f4d9d8d..b22859dafd 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/loop_classes.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/loop_classes.rs @@ -5,23 +5,12 @@ use pyo3::types::{PyDict, PyDictMethods, PyFrozenSet, PyList, PyTuple}; use pyo3::{IntoPyObjectExt, pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; const AUDITING_TYPES_MODULE: &str = "sqlbuild.compiler.auditing.types"; -const DISCOVERY_MODELS_MODULE: &str = "sqlbuild._native"; const LINEAGE_TYPES_MODULE: &str = "sqlbuild.compiler.lineage.types"; -const PLANNER_TYPES_MODULE: &str = "sqlbuild._native"; -const REFERENCE_TYPES_MODULE: &str = "sqlbuild._native"; -const SCOPE_ERRORS_MODULE: &str = "sqlbuild._native"; -const SCOPE_MODELS_MODULE: &str = "sqlbuild._native"; -const SCOPE_TYPES_MODULE: &str = "sqlbuild._native"; -const CONTRACT_MODELS_MODULE: &str = "sqlbuild._native"; -const CONTRACT_TYPES_MODULE: &str = "sqlbuild._native"; -const NATIVE_MODULE: &str = "sqlbuild._native"; /// The materializations a model's storage policies are inherited for, besides none. const TABLE_BACKED_MATERIALIZATIONS: [&str; 3] = ["TABLE", "INCREMENTAL", "SNAPSHOT"]; /// The declaration kinds whose project-owned records the loop names. @@ -31,14 +20,6 @@ fn class<'py>(py: Python<'py>, module: &str, name: &str) -> PyResult( - py: Python<'py>, - module: &str, - (enum_class, name): (&str, &str), -) -> PyResult> { - imported(py, module, enum_class)?.getattr(name) -} - fn dict<'py>( py: Python<'py>, entries: Vec<(&str, Bound<'py, PyAny>)>, @@ -54,7 +35,7 @@ fn dict<'py>( pub(crate) fn table_backed<'py>(py: Python<'py>) -> PyResult>> { TABLE_BACKED_MATERIALIZATIONS .iter() - .map(|name| member(py, PLANNER_TYPES_MODULE, ("MaterializationType", name))) + .map(|name| registry::member(py, RecordId::MaterializationType, name)) .collect() } @@ -76,28 +57,24 @@ pub(crate) fn native_config_classes<'py>( ("table_backed", PyTuple::new(py, table_backed)?.into_any()), ( "compile_model_config", - class(py, NATIVE_RECORDS_MODULE, "CompileModelConfig")?, + registry::class(py, RecordId::CompileModelConfig)?, ), ( "resolved_retention", - class(py, CONTRACT_MODELS_MODULE, "ResolvedTimeTravelRetention")?, + registry::class(py, RecordId::ResolvedTimeTravelRetention)?, ), ( "retention_source", - member( - py, - CONTRACT_TYPES_MODULE, - ("TimeTravelRetentionSource", "MODEL"), - )?, + registry::member(py, RecordId::TimeTravelRetentionSource, "MODEL")?, ), ( "resolved_table_type", - class(py, CONTRACT_MODELS_MODULE, "ResolvedTableType")?, + registry::class(py, RecordId::ResolvedTableType)?, ), - ("table_type", class(py, CONTRACT_TYPES_MODULE, "TableType")?), + ("table_type", registry::class(py, RecordId::TableType)?), ( "table_type_source", - member(py, CONTRACT_TYPES_MODULE, ("TableTypeSource", "MODEL"))?, + registry::member(py, RecordId::TableTypeSource, "MODEL")?, ), ("record_template_reads", record_template_reads.clone()), ], @@ -113,7 +90,7 @@ fn named_records<'py>(resolver: &Bound<'py, PyAny>) -> PyResult> = NAMED_KINDS .iter() - .map(|name| member(py, SCOPE_TYPES_MODULE, ("DeclarationKind", name))) + .map(|name| registry::member(py, RecordId::DeclarationKind, name)) .collect::>()?; let declarations: Bound<'py, PyAny> = resolver.getattr("lookup")?.getattr("declarations")?; for pair in declarations.call_method0("items")?.try_iter()? { @@ -139,8 +116,8 @@ fn schema_enums<'py>(resolver: &Bound<'py, PyAny>) -> PyResult = member(py, SCOPE_TYPES_MODULE, ("DeclarationKind", "ENUM"))?; - let enum_class: Bound<'py, PyAny> = class(py, DISCOVERY_MODELS_MODULE, "EnumDeclaration")?; + let enum_kind: Bound<'py, PyAny> = registry::member(py, RecordId::DeclarationKind, "ENUM")?; + let enum_class: Bound<'py, PyAny> = registry::class(py, RecordId::EnumDeclaration)?; let declarations: Bound<'py, PyAny> = resolver.getattr("projection")?.getattr("declarations")?; for pair in declarations.call_method0("items")?.try_iter()? { @@ -193,7 +170,7 @@ fn visibility_classes<'py>( } else { let lookup: Bound<'py, PyAny> = resolver.getattr("lookup")?; let declarations: Bound<'py, PyAny> = lookup.getattr("index")?.getattr("declarations")?; - let private: Bound<'py, PyAny> = member(py, SCOPE_TYPES_MODULE, ("ScopeKind", "PRIVATE"))?; + let private: Bound<'py, PyAny> = registry::member(py, RecordId::ScopeKind, "PRIVATE")?; let mut owners: Vec> = Vec::new(); for declaration in declarations.try_iter()? { let declaration: Bound<'py, PyAny> = declaration?; @@ -218,35 +195,31 @@ fn visibility_classes<'py>( ("visible_declarations", visible_declarations.clone()), ( "resource_identity", - class(py, SCOPE_MODELS_MODULE, "ResourceIdentity")?, + registry::class(py, RecordId::ResourceIdentity)?, ), ( "declaration_identity", - class(py, SCOPE_MODELS_MODULE, "DeclarationIdentity")?, + registry::class(py, RecordId::DeclarationIdentity)?, ), ( "model_kind", - member(py, SCOPE_TYPES_MODULE, ("ResourceKind", "MODEL"))?, + registry::member(py, RecordId::ResourceKind, "MODEL")?, ), ( "enum_kind", - member(py, SCOPE_TYPES_MODULE, ("DeclarationKind", "ENUM"))?, + registry::member(py, RecordId::DeclarationKind, "ENUM")?, ), ( "constant_kind", - member(py, SCOPE_TYPES_MODULE, ("DeclarationKind", "CONSTANT"))?, + registry::member(py, RecordId::DeclarationKind, "CONSTANT")?, ), ( "private_owner", - member( - py, - SCOPE_TYPES_MODULE, - ("VisibilityReason", "PRIVATE_OWNER"), - )?, + registry::member(py, RecordId::VisibilityReason, "PRIVATE_OWNER")?, ), ( "visibility_record", - class(py, SCOPE_MODELS_MODULE, "VisibilityRecord")?, + registry::class(py, RecordId::VisibilityRecord)?, ), ], ) @@ -256,15 +229,15 @@ fn input_view_classes<'py>(py: Python<'py>) -> PyResult> { let compiled: Vec<(&str, Bound<'py, PyAny>)> = vec![ ( "object_key", - class(py, COMPILE_MODELS_MODULE, "CompiledObjectKey")?, + registry::class(py, RecordId::CompiledObjectKey)?, ), ( "relation_location", - class(py, COMPILE_MODELS_MODULE, "CompiledRelationLocation")?, + registry::class(py, RecordId::CompiledRelationLocation)?, ), ( "inferred_column", - class(py, COMPILE_MODELS_MODULE, "InferredColumn")?, + registry::class(py, RecordId::InferredColumn)?, ), ( "nullability", @@ -272,31 +245,31 @@ fn input_view_classes<'py>(py: Python<'py>) -> PyResult> { ), ( "compiler_diagnostic", - class(py, COMPILE_MODELS_MODULE, "CompilerDiagnostic")?, + registry::class(py, RecordId::CompilerDiagnostic)?, ), ( "source_location", - class(py, CONTRACT_MODELS_MODULE, "SourceLocation")?, + registry::class(py, RecordId::SourceLocation)?, ), ( "compact_lineage_facts", - class(py, COMPILE_MODELS_MODULE, "CompactLineageFacts")?, + registry::class(py, RecordId::CompactLineageFacts)?, ), ( "lineage_column_fact", - class(py, NATIVE_RECORDS_MODULE, "CompiledLineageColumnFact")?, + registry::class(py, RecordId::CompiledLineageColumnFact)?, ), ( "lineage_source_fact", - class(py, COMPILE_MODELS_MODULE, "CompiledLineageSourceFact")?, + registry::class(py, RecordId::CompiledLineageSourceFact)?, ), ( "contract_proof", - class(py, NATIVE_RECORDS_MODULE, "DynamicColumnContractProof")?, + registry::class(py, RecordId::DynamicColumnContractProof)?, ), ( "family_proof", - class(py, NATIVE_RECORDS_MODULE, "DynamicColumnFamilyProof")?, + registry::class(py, RecordId::DynamicColumnFamilyProof)?, ), ]; dict( @@ -304,23 +277,23 @@ fn input_view_classes<'py>(py: Python<'py>) -> PyResult> { vec![ ( "sql_reference", - class(py, COMPILE_MODELS_MODULE, "CompileSqlReference")?, + registry::class(py, RecordId::CompileSqlReference)?, ), ( "reference_kind", - class(py, REFERENCE_TYPES_MODULE, "SqlReferenceKind")?, + registry::class(py, RecordId::SqlReferenceKind)?, ), ( "schema_model_entry", - class(py, CONTRACT_MODELS_MODULE, "SchemaModelEntry")?, + registry::class(py, RecordId::SchemaModelEntry)?, ), ( "sql_expansion", - class(py, NATIVE_RECORDS_MODULE, "CompiledSqlExpansion")?, + registry::class(py, RecordId::CompiledSqlExpansion)?, ), ( "expansion_span", - class(py, NATIVE_RECORDS_MODULE, "ExpansionSpan")?, + registry::class(py, RecordId::ExpansionSpan)?, ), ( "audits", @@ -346,11 +319,11 @@ fn input_view_classes<'py>(py: Python<'py>) -> PyResult> { vec![ ( "function_argument", - class(py, NATIVE_RECORDS_MODULE, "FunctionArgument")?, + registry::class(py, RecordId::FunctionArgument)?, ), ( "function_return_column", - class(py, NATIVE_RECORDS_MODULE, "FunctionReturnColumn")?, + registry::class(py, RecordId::FunctionReturnColumn)?, ), ( "function_language", @@ -403,7 +376,7 @@ pub(crate) fn model_loop_classes<'py>( ("provider_names", provider_names.into_bound_py_any(py)?), ( "python_hook_entry", - class(py, DISCOVERY_MODELS_MODULE, "PythonHookEntry")?, + registry::class(py, RecordId::PythonHookEntry)?, ), ( "signature", @@ -417,32 +390,26 @@ pub(crate) fn model_loop_classes<'py>( ("replace", imported(py, "dataclasses", "replace")?), ( "declaration_rows", - imported(py, NATIVE_MODULE, "declaration_rows")?, + registry::native(py, NativeAttr::DeclarationRows)?, ), ], )?; dict( py, vec![ - ( - "usage_record", - class(py, SCOPE_MODELS_MODULE, "UsageRecord")?, - ), - ( - "compile_error", - class(py, COMPILE_ERRORS_MODULE, "CompileInputError")?, - ), + ("usage_record", registry::class(py, RecordId::UsageRecord)?), + ("compile_error", registry::error(py, ErrorId::CompileInput)?), ("syntax_help", host("syntax_help")?), ("source_syntax_help", host("source_syntax_help")?), ("macros", host("macros")?), ("sql_hooks", host("sql_hooks")?), ( "scope_path_error", - class(py, SCOPE_ERRORS_MODULE, "InvalidScopePathError")?, + registry::error(py, ErrorId::InvalidScopePath)?, ), ( "sql_hook_entry", - class(py, DISCOVERY_MODELS_MODULE, "SqlHookEntry")?, + registry::class(py, RecordId::SqlHookEntry)?, ), ( "audit_factories", @@ -450,25 +417,25 @@ pub(crate) fn model_loop_classes<'py>( ), ( "generated_usage", - member(py, SCOPE_TYPES_MODULE, ("UsageKind", "GENERATED"))?, + registry::member(py, RecordId::UsageKind, "GENERATED")?, ), ( "contract_enforced", - member(py, PLANNER_TYPES_MODULE, ("ContractPolicy", "ENFORCED"))?, + registry::member(py, RecordId::ContractPolicy, "ENFORCED")?, ), ("model_schemas", context.getattr("public_model_schemas")?), ("named_records", named_records(&resolver)?.into_any()), ("schema_enums", schema_enums(&resolver)?.into_any()), ( "compiler_diagnostic", - class(py, COMPILE_MODELS_MODULE, "CompilerDiagnostic")?, + registry::class(py, RecordId::CompilerDiagnostic)?, ), ("report_diagnostic", host("report_diagnostic")?), ("path_class", imported(py, "pathlib", "Path")?), ("external_resolver", external_resolver.clone()), ( "reference_kind", - class(py, REFERENCE_TYPES_MODULE, "SqlReferenceKind")?, + registry::class(py, RecordId::SqlReferenceKind)?, ), ("partial", imported(py, "functools", "partial")?), ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/records.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/records.rs index 846deceaae..79c7d56990 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/records.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/records.rs @@ -1,6 +1,5 @@ //! Compiled records and views: projecting native views as records, and recording later stages. -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class_at; use pyo3::prelude::{ Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyRef, PyResult, Python, }; @@ -13,10 +12,9 @@ use crate::bindings::_helpers::compiled_project::inputs_view::NativeInputsView; use crate::bindings::_helpers::compiled_project::model_input_view::NativeModelInput; use crate::bindings::_helpers::compiled_project::project::NativeCompiledProject; use crate::bindings::_helpers::compiled_project::project_view::NativeProjectView; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const LINEAGE_TYPES_MODULE: &str = "sqlbuild.compiler.lineage.types"; const DATACLASSES_MODULE: &str = "dataclasses"; const DECIMAL_KIND: &str = "decimal"; @@ -37,9 +35,9 @@ pub(crate) fn replace<'py>( } /// `value` when it already is a `class` record, else `class(**{field: getattr(value, field)})`. -fn record<'py>(value: &Bound<'py, PyAny>, class: &str) -> PyResult> { +fn record<'py>(value: &Bound<'py, PyAny>, class: RecordId) -> PyResult> { let py: Python<'py> = value.py(); - let class: Bound<'py, PyAny> = shape_class_at(py, COMPILE_MODELS_MODULE, class)?; + let class: Bound<'py, PyAny> = registry::class(py, class)?; if value.is_instance(&class)? { return Ok(value.clone()); } @@ -60,13 +58,13 @@ fn record<'py>(value: &Bound<'py, PyAny>, class: &str) -> PyResult(model: &Bound<'py, PyAny>) -> PyResult> { - record(model, "CompiledModel") + record(model, RecordId::CompiledModel) } /// The audit as a `CompiledAudit`, projected from its owner when it is a native view. #[pyfunction] pub(crate) fn compiled_audit_record<'py>(audit: &Bound<'py, PyAny>) -> PyResult> { - record(audit, "CompiledAudit") + record(audit, RecordId::CompiledAudit) } /// The function as a `CompiledFunction`, projected from its owner when it is a native view. @@ -74,7 +72,7 @@ pub(crate) fn compiled_audit_record<'py>(audit: &Bound<'py, PyAny>) -> PyResult< pub(crate) fn compiled_function_record<'py>( function: &Bound<'py, PyAny>, ) -> PyResult> { - record(function, "CompiledFunction") + record(function, RecordId::CompiledFunction) } /// The source as a `CompiledSource`, projected from its owner when it is a native view. @@ -82,17 +80,17 @@ pub(crate) fn compiled_function_record<'py>( pub(crate) fn compiled_source_record<'py>( source: &Bound<'py, PyAny>, ) -> PyResult> { - record(source, "CompiledSource") + record(source, RecordId::CompiledSource) } /// The seed as a `CompiledSeed`, projected from its owner when it is a native view. #[pyfunction] pub(crate) fn compiled_seed_record<'py>(seed: &Bound<'py, PyAny>) -> PyResult> { - record(seed, "CompiledSeed") + record(seed, RecordId::CompiledSeed) } /// Each item of `fields[name]` as a `class` record. -fn records_of<'py>(fields: &Bound<'py, PyDict>, (name, class): (&str, &str)) -> PyResult<()> { +fn records_of<'py>(fields: &Bound<'py, PyDict>, (name, class): (&str, RecordId)) -> PyResult<()> { let py: Python<'py> = fields.py(); let items: Vec> = fields .as_any() @@ -111,7 +109,7 @@ pub(crate) fn compiled_project_record<'py>( include_sql_expansions: bool, ) -> PyResult> { let py: Python<'py> = project.py(); - let class: Bound<'py, PyAny> = imported(py, COMPILE_MODELS_MODULE, "CompiledProject")?; + let class: Bound<'py, PyAny> = registry::class(py, RecordId::CompiledProject)?; if project.is_instance(&class)? { if include_sql_expansions { return Ok(project.clone()); @@ -122,10 +120,10 @@ pub(crate) fn compiled_project_record<'py>( let fields: Bound<'py, PyDict> = view.fields(py, include_sql_expansions)?; drop(view); for pair in [ - ("seeds", "CompiledSeed"), - ("sources", "CompiledSource"), - ("functions", "CompiledFunction"), - ("audits", "CompiledAudit"), + ("seeds", RecordId::CompiledSeed), + ("sources", RecordId::CompiledSource), + ("functions", RecordId::CompiledFunction), + ("audits", RecordId::CompiledAudit), ] { records_of(&fields, pair)?; } @@ -140,7 +138,7 @@ pub(crate) fn with_binding_diagnostics<'py>( diagnostics: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = model.py(); - if model.is_instance(&imported(py, COMPILE_MODELS_MODULE, "CompiledModel")?)? { + if model.is_instance(®istry::class(py, RecordId::CompiledModel)?)? { return replace(model, &[("binding_diagnostics", diagnostics.clone())]); } model.call_method1("with_binding_diagnostics", (diagnostics,)) @@ -163,7 +161,7 @@ pub(crate) fn with_type_recovery<'py>( unchecked.push(column); } } - if !model.is_instance(&imported(py, COMPILE_MODELS_MODULE, "CompiledModel")?)? { + if !model.is_instance(®istry::class(py, RecordId::CompiledModel)?)? { return model.call_method1("with_type_recovery", (diagnostics, unchecked)); } let inferred: Bound<'py, PyAny> = model.getattr("inferred_columns")?; @@ -202,7 +200,7 @@ pub(crate) fn project_with<'py>( project: &Bound<'py, PyAny>, changes: Option>, ) -> PyResult> { - with_changes(project, "CompiledProject", changes) + with_changes(project, RecordId::CompiledProject, changes) } /// The compile inputs with `changes`; a native view shares its owner's model inputs. @@ -212,18 +210,18 @@ pub(crate) fn inputs_with<'py>( inputs: &Bound<'py, PyAny>, changes: Option>, ) -> PyResult> { - with_changes(inputs, "CompileProjectInputs", changes) + with_changes(inputs, RecordId::CompileProjectInputs, changes) } /// `replace(value, **changes)` for a record, else the native view's `with_changes(**changes)`. fn with_changes<'py>( value: &Bound<'py, PyAny>, - class: &str, + class: RecordId, changes: Option>, ) -> PyResult> { let py: Python<'py> = value.py(); let changes: Bound<'py, PyDict> = changes.unwrap_or_else(|| PyDict::new(py)); - if value.is_instance(&shape_class_at(py, COMPILE_MODELS_MODULE, class)?)? { + if value.is_instance(®istry::class(py, class)?)? { return imported(py, DATACLASSES_MODULE, "replace")?.call((value,), Some(&changes)); } value.call_method("with_changes", (), Some(&changes)) @@ -253,7 +251,7 @@ pub(crate) fn compile_inputs_view<'py>( } let keywords: Bound<'py, PyDict> = fields.copy()?; keywords.set_item("native_project", native_project)?; - imported(py, NATIVE_RECORDS_MODULE, "CompileProjectInputs")?.call((), Some(&keywords)) + registry::class(py, RecordId::CompileProjectInputs)?.call((), Some(&keywords)) } /// A typed SQL value as the plain value native consumers read. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiler_records/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiler_records/constants.rs index b34dea553e..63489d0281 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiler_records/constants.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiler_records/constants.rs @@ -1,5 +1,7 @@ //! Layouts of the compiler, lint and config value records hosted natively. +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::records::models::{FieldDefault, FieldSpec, RecordSpec}; const R_NATIVE_MODEL_ANALYSIS_REQUEST: RecordSpec = RecordSpec { @@ -132,7 +134,7 @@ const R_HOOK_READ_EDGE: RecordSpec = RecordSpec { FieldSpec::required("read"), FieldSpec::new( "hook_type", - FieldDefault::Member("sqlbuild._native", "HookReadType", "PYTHON"), + FieldDefault::NativeMember(RecordId::HookReadType, "PYTHON"), ), FieldSpec::new("hook_path", FieldDefault::None), ], @@ -163,11 +165,11 @@ const R_COLUMN_LINEAGE_EDGE: RecordSpec = RecordSpec { FieldSpec::required("target"), FieldSpec::new( "transform_kind", - FieldDefault::Member("sqlbuild._native", "ColumnTransformKind", "UNKNOWN"), + FieldDefault::NativeMember(RecordId::ColumnTransformKind, "UNKNOWN"), ), FieldSpec::new( "confidence", - FieldDefault::Member("sqlbuild._native", "ColumnLineageConfidence", "UNKNOWN"), + FieldDefault::NativeMember(RecordId::ColumnLineageConfidence, "UNKNOWN"), ), ], }; @@ -181,7 +183,7 @@ const R_DIRECT_SEMANTIC_COLUMN_USE: RecordSpec = RecordSpec { FieldSpec::required("source"), FieldSpec::new( "confidence", - FieldDefault::Member("sqlbuild._native", "ColumnLineageConfidence", "UNKNOWN"), + FieldDefault::NativeMember(RecordId::ColumnLineageConfidence, "UNKNOWN"), ), FieldSpec::new("line", FieldDefault::None), FieldSpec::new("column", FieldDefault::None), @@ -205,7 +207,7 @@ const R_COLUMN_LINEAGE: RecordSpec = RecordSpec { ), FieldSpec::new( "confidence", - FieldDefault::Member("sqlbuild._native", "ColumnLineageConfidence", "UNKNOWN"), + FieldDefault::NativeMember(RecordId::ColumnLineageConfidence, "UNKNOWN"), ), ], }; @@ -491,7 +493,7 @@ const R_COMPILE_PIPELINE_OPTIONS: RecordSpec = RecordSpec { FieldSpec::new("accepts_unit_test_selectors", FieldDefault::False), FieldSpec::new( "sql_test_selection", - FieldDefault::Call("sqlbuild._native", "SqlTestSelection"), + FieldDefault::NativeCall(RecordId::SqlTestSelection), ), ], }; @@ -504,7 +506,7 @@ const R_PYTHON_PLAN_ENTRY: RecordSpec = RecordSpec { FieldSpec::required("phase"), FieldSpec::new( "identity_status", - FieldDefault::Member("sqlbuild._native", "PythonIdentityStatus", "UNKNOWN"), + FieldDefault::NativeMember(RecordId::PythonIdentityStatus, "UNKNOWN"), ), FieldSpec::new("current_definition_json", FieldDefault::None), FieldSpec::new("previous_definition_json", FieldDefault::None), @@ -586,7 +588,7 @@ const R_DISCOVERED_PYTHON_LOADER_METADATA: RecordSpec = RecordSpec { FieldSpec::new("contract", FieldDefault::None), FieldSpec::new( "connection_mode", - FieldDefault::Member("sqlbuild._native", "LoaderConnectionMode", "SQLBUILD"), + FieldDefault::NativeMember(RecordId::LoaderConnectionMode, "SQLBUILD"), ), ], }; @@ -609,11 +611,7 @@ const R_DISCOVERED_PYTHON_CHECK_METADATA: RecordSpec = RecordSpec { doc: "Check-specific metadata carried by an internal Python check node.", fields: &[FieldSpec::new( "severity", - FieldDefault::Member( - "sqlbuild.python_nodes.types", - "PythonCheckSeverity", - "ERROR", - ), + FieldDefault::HostMember(UserCodeHost::PythonCheckSeverity.id(), "ERROR"), )], }; const R_PYTHON_NODE_DEPENDENCY_EDGE: RecordSpec = RecordSpec { @@ -727,7 +725,7 @@ const R_DBT_INIT_REQUEST: RecordSpec = RecordSpec { FieldSpec::new("skip_dbt_debug", FieldDefault::False), FieldSpec::new( "progress_callbacks", - FieldDefault::Call("sqlbuild._native", "DbtInitProgressCallbacks"), + FieldDefault::NativeCall(RecordId::DbtInitProgressCallbacks), ), ], }; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiler_records/mod.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiler_records/mod.rs index 16fd0baad8..61d449b063 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiler_records/mod.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiler_records/mod.rs @@ -1,2 +1 @@ pub(crate) mod constants; -pub(crate) mod shape_classes; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiler_records/shape_classes.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiler_records/shape_classes.rs deleted file mode 100644 index a9e9ea9d42..0000000000 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiler_records/shape_classes.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! Resolve a shape class by its Python module and name: the native class once it is hosted natively. - -use pyo3::exceptions::PyLookupError; -use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; - -use sqlbuild_cache::hosted_shapes::main::hosted_native_name::hosted_native_name; - -use crate::bindings::_helpers::compile_driver::python_calls::imported; -use crate::bindings::_helpers::records::constants::NATIVE_MODULE; - -/// The native class name hosting `module:class`, failing loudly when the name is ambiguous. -fn hosted_name(module: &str, class: &str) -> PyResult> { - hosted_native_name(module, class).map_err(|_| { - PyLookupError::new_err(format!( - "{module}:{class} names more than one natively hosted shape" - )) - }) -} - -/// `class` of the Python module object `module`, or the native class hosting it. -pub(crate) fn shape_class<'py, T>( - module: &Bound<'py, T>, - class: &str, -) -> PyResult> { - let module_path: String = module.as_any().getattr("__name__")?.extract()?; - if let Some(native) = hosted_name(&module_path, class)? { - return imported(module.py(), NATIVE_MODULE, native); - } - module.as_any().getattr(class) -} - -/// `class` of the module at `module_path`, or the native class hosting it. -pub(crate) fn shape_class_at<'py>( - py: Python<'py>, - module_path: &str, - class: &str, -) -> PyResult> { - if let Some(native) = hosted_name(module_path, class)? { - return imported(py, NATIVE_MODULE, native); - } - imported(py, module_path, class) -} diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compute_logs/conversion.rs b/crates/sqlbuild-python/src/bindings/_helpers/compute_logs/conversion.rs index 5b18e88d17..03e5e15825 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compute_logs/conversion.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compute_logs/conversion.rs @@ -10,33 +10,30 @@ use sqlbuild_cli::compute_logs::models::{ }; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; -pub(crate) const EXCEPTIONS_MODULE: &str = "sqlbuild.runtime.compute_logs.exceptions"; -pub(crate) const MODELS_MODULE: &str = "sqlbuild.runtime.compute_logs.models"; -pub(crate) const TYPES_MODULE: &str = "sqlbuild.runtime.compute_logs.types"; const EPOCH_TEXT: &str = "1970-01-01T00:00:00+00:00"; const UTC_OFFSET: &str = "+00:00"; const UTC_SUFFIX: &str = "Z"; const STREAM_VALUES: [&str; 3] = ["stdout", "stderr", "diagnostics"]; -fn exception_name(kind: ComputeLogErrorKind) -> &'static str { +fn exception_class(kind: ComputeLogErrorKind) -> ExecutorHost { match kind { - ComputeLogErrorKind::InvalidCaptureId => "InvalidCaptureIdError", - ComputeLogErrorKind::InvalidCursor => "InvalidComputeLogCursorError", - ComputeLogErrorKind::InvalidLimit => "InvalidComputeLogLimitError", - ComputeLogErrorKind::AlreadyExists => "CaptureAlreadyExistsError", - ComputeLogErrorKind::NotFound => "CaptureNotFoundError", - ComputeLogErrorKind::State => "CaptureStateError", - ComputeLogErrorKind::Path => "ComputeLogPathError", - ComputeLogErrorKind::Metadata => "ComputeLogMetadataError", + ComputeLogErrorKind::InvalidCaptureId => ExecutorHost::InvalidCaptureIdError, + ComputeLogErrorKind::InvalidCursor => ExecutorHost::InvalidComputeLogCursorError, + ComputeLogErrorKind::InvalidLimit => ExecutorHost::InvalidComputeLogLimitError, + ComputeLogErrorKind::AlreadyExists => ExecutorHost::CaptureAlreadyExistsError, + ComputeLogErrorKind::NotFound => ExecutorHost::CaptureNotFoundError, + ComputeLogErrorKind::State => ExecutorHost::CaptureStateError, + ComputeLogErrorKind::Path => ExecutorHost::ComputeLogPathError, + ComputeLogErrorKind::Metadata => ExecutorHost::ComputeLogMetadataError, } } /// The Python exception for a storage rejection of `kind`. pub(crate) fn rejection(py: Python<'_>, kind: ComputeLogErrorKind, message: &str) -> PyErr { - match imported(py, EXCEPTIONS_MODULE, exception_name(kind)) - .and_then(|class| class.call1((message,))) - { + match host::entry(py, exception_class(kind)).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } @@ -70,7 +67,7 @@ pub(crate) fn capture_id(value: &Bound<'_, PyAny>) -> PyResult { /// The stream index of a `ComputeLogStream` member: stdout, stderr or diagnostics. pub(crate) fn stream_index(stream: &Bound<'_, PyAny>) -> PyResult { let py = stream.py(); - if stream.is_instance(&imported(py, TYPES_MODULE, "ComputeLogStream")?)? { + if stream.is_instance(&host::entry(py, ExecutorHost::ComputeLogStream)?)? { let value: String = stream.getattr("value")?.extract()?; if let Some(index) = STREAM_VALUES.iter().position(|name| *name == value) { return Ok(index); @@ -129,7 +126,7 @@ pub(crate) fn document(metadata: &Bound<'_, PyAny>) -> PyResult i64::from(version.eq(1)?) }; let completion = - if metadata.is_instance(&imported(py, MODELS_MODULE, "FinalCaptureMetadata")?)? { + if metadata.is_instance(&host::entry(py, ExecutorHost::FinalCaptureMetadata)?)? { Some(CaptureCompletion { completed_at: instant(&metadata.getattr("completed_at")?)?, exit_code: count(py, &metadata.getattr("exit_code")?)?, @@ -197,7 +194,7 @@ pub(crate) fn metadata_model<'py>( ("run_id", document.run_id.as_deref().into_bound_py_any(py)?), ]; let Some(completion) = &document.completion else { - return call_with(&imported(py, MODELS_MODULE, "CaptureMetadata")?, &fields); + return call_with(&host::entry(py, ExecutorHost::CaptureMetadata)?, &fields); }; fields.extend([ ( @@ -219,7 +216,7 @@ pub(crate) fn metadata_model<'py>( ), ]); call_with( - &imported(py, MODELS_MODULE, "FinalCaptureMetadata")?, + &host::entry(py, ExecutorHost::FinalCaptureMetadata)?, &fields, ) } @@ -230,7 +227,7 @@ pub(crate) fn inventory_item<'py>( entry: &InventoryEntry, ) -> PyResult> { call_with( - &imported(py, MODELS_MODULE, "CaptureInventoryItem")?, + &host::entry(py, ExecutorHost::CaptureInventoryItem)?, &[ ( "invocation_id", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compute_logs/storage.rs b/crates/sqlbuild-python/src/bindings/_helpers/compute_logs/storage.rs index a722957f46..407cda8b7e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compute_logs/storage.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compute_logs/storage.rs @@ -32,12 +32,12 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, }; use crate::bindings::_helpers::compute_logs::conversion::{ - MODELS_MODULE, capture_id, document, inventory_item, is_exact_int, metadata_model, rejection, - storage_error, stream_index, + capture_id, document, inventory_item, is_exact_int, metadata_model, rejection, storage_error, + stream_index, }; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; -const DIAGNOSTIC_MODEL_MODULE: &str = "sqlbuild.runtime.observability.models"; -const DIAGNOSTIC_JSON_MODULE: &str = "sqlbuild.runtime.observability.main.diagnostic_log_to_json"; const DIAGNOSTICS_STREAM: usize = 2; /// Open stream files of the captures this instance started, and whether it is closed. @@ -221,7 +221,7 @@ impl LocalFilesystemComputeLogStorage { invocation_id: &Bound<'_, PyAny>, record: &Bound<'_, PyAny>, ) -> PyResult<()> { - if !record.is_instance(&imported(py, DIAGNOSTIC_MODEL_MODULE, "DiagnosticLog")?)? { + if !record.is_instance(&host::entry(py, ExecutorHost::DiagnosticLog)?)? { return Err(rejection( py, ComputeLogErrorKind::State, @@ -229,7 +229,7 @@ impl LocalFilesystemComputeLogStorage { )); } let encoded: Bound<'_, PyAny> = - imported(py, DIAGNOSTIC_JSON_MODULE, "diagnostic_log_to_json")?.call1((record,))?; + host::entry(py, ExecutorHost::DiagnosticLogToJson)?.call1((record,))?; let mut line: Vec = encoded .downcast::()? .to_str()? @@ -276,7 +276,7 @@ impl LocalFilesystemComputeLogStorage { ), )?; call_with( - &imported(py, MODELS_MODULE, "ComputeLogReadChunk")?, + &host::entry(py, ExecutorHost::ComputeLogReadChunk)?, &[ ("data", PyBytes::new(py, &chunk.data).into_any()), ("next_cursor", chunk.next_cursor.into_bound_py_any(py)?), @@ -319,7 +319,7 @@ impl LocalFilesystemComputeLogStorage { let [stdout_bytes, stderr_bytes, diagnostics_bytes] = self.byte_counts(py, invocation_id)?; call_with( - &imported(py, MODELS_MODULE, "CaptureByteCounts")?, + &host::entry(py, ExecutorHost::CaptureByteCounts)?, &[ ("stdout_bytes", stdout_bytes.into_bound_py_any(py)?), ("stderr_bytes", stderr_bytes.into_bound_py_any(py)?), @@ -396,7 +396,7 @@ impl LocalFilesystemComputeLogStorage { py.detach(|| prune_captures(&root, retain, &|id: &str| active.contains(id))), )?; call_with( - &imported(py, MODELS_MODULE, "PruneResult")?, + &host::entry(py, ExecutorHost::PruneResult)?, &[ ( "deleted_invocation_ids", @@ -423,7 +423,7 @@ impl LocalFilesystemComputeLogStorage { } let complete_count = entries.iter().filter(|entry| entry.is_complete).count(); call_with( - &imported(py, MODELS_MODULE, "CaptureInventory")?, + &host::entry(py, ExecutorHost::CaptureInventory)?, &[ ("captures", PyTuple::new(py, items)?.into_any()), ("complete_count", complete_count.into_bound_py_any(py)?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/contract_command/command.rs b/crates/sqlbuild-python/src/bindings/_helpers/contract_command/command.rs index 606b246d5d..c32675ba79 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/contract_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/contract_command/command.rs @@ -23,14 +23,9 @@ use crate::bindings::_helpers::command_runtime::invocation::resolve_adapter_conn use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; use crate::bindings::_helpers::contract_command::write::{ContractWrite, write_contracts}; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ERRORS_MODULE: &str = "sqlbuild._native"; -const REJECT_UNIT_TESTS_MODULE: &str = "sqlbuild._native"; -const GRAPH_SELECTION_MODULE: &str = "sqlbuild._native"; -const ADOPTION_MODELS: &str = "sqlbuild._native"; const GENERATE_ACTION: &str = "generate"; /// A `CliUserError` with its code. @@ -38,7 +33,7 @@ fn user_error(py: Python<'_>, message: &str, code: &str) -> PyErr { let raised = (|| { let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("code", code)?; - imported(py, ERRORS_MODULE, "CliUserError")?.call((message,), Some(&options)) + registry::error(py, ErrorId::CliUser)?.call((message,), Some(&options)) })(); match raised { Ok(error) => PyErr::from_value(error), @@ -81,11 +76,11 @@ fn select_resources<'py>( ) -> PyResult> { let py: Python<'py> = request.py(); let analysis: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "CompileAnalysisSelection")?, + ®istry::class(py, RecordId::CompileAnalysisSelection)?, &[("no_cache", PyBool::new(py, true).to_owned().into_any())], )?; let project: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "build_compiled_project")?, + ®istry::native(py, NativeAttr::BuildCompiledProject)?, &[ ("discovered_inputs", discovered.clone()), ("adapter", context.getattr("adapter")?), @@ -96,15 +91,11 @@ fn select_resources<'py>( ], )?; let graph: Bound<'py, PyAny> = call_with( - &imported( - py, - NATIVE_MODULE, - "build_project_graph_from_compiled_project", - )?, + ®istry::native(py, NativeAttr::BuildProjectGraphFromCompiledProject)?, &[("project", project)], )?; let selected: Bound<'py, PyAny> = call_with( - &imported(py, GRAPH_SELECTION_MODULE, "select_project_graph")?, + ®istry::native(py, NativeAttr::SelectProjectGraph)?, &[ ("graph", graph.clone()), ("select", request.getattr("select")?), @@ -169,7 +160,7 @@ fn compare<'py>( let py: Python<'py> = request.py(); let adapter: Bound<'py, PyAny> = context.getattr("adapter")?; let reporter: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "ConnectionProgressReporter")?, + ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, &[ ("adapter_name", context.getattr("adapter_name")?), ("stream", progress.stream.clone()), @@ -207,7 +198,7 @@ fn compare<'py>( ),), )?; let evidence: Bound<'py, PyAny> = match call_with( - &imported(py, NATIVE_MODULE, "compare_contracts")?, + ®istry::native(py, NativeAttr::CompareContracts)?, &[ ("adapter", adapter.clone()), ("connection", connection.clone()), @@ -371,11 +362,11 @@ fn run_contract_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult< imported(py, "pathlib", "Path")?.call_method0("cwd")? }; let discovered: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[("project_dir", project_dir.clone())], )?; call_with( - &imported(py, REJECT_UNIT_TESTS_MODULE, "reject_unit_test_selectors")?, + ®istry::native(py, NativeAttr::RejectUnitTestSelectors)?, &[ ("select", request.getattr("select")?), ("exclude", request.getattr("exclude")?), @@ -404,7 +395,7 @@ fn run_contract_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult< let progress: Progress<'_> = progress_for(request)?; let evidence: Bound<'_, PyAny> = compare(request, &context, &selection, &progress)?; let mut result: Bound<'_, PyAny> = call_with( - &imported(py, ADOPTION_MODELS, "ContractAdoptionResult")?, + ®istry::class(py, RecordId::ContractAdoptionResult)?, &[("from_target", from_target), ("evidence", evidence)], )?; if generate_write { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/contract_command/write.rs b/crates/sqlbuild-python/src/bindings/_helpers/contract_command/write.rs index bc88c90805..f563b0dd6a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/contract_command/write.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/contract_command/write.rs @@ -5,10 +5,9 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyList, PyListMethods, PyTuple}; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; const UTF8: &str = "utf-8"; @@ -28,7 +27,6 @@ pub(crate) fn write_contracts<'py>( result: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = graph.py(); - let native = py.import(NATIVE_MODULE)?; let project: Bound<'py, PyAny> = graph.getattr("project")?; let models: Bound<'py, PyDict> = by_name(&project.getattr("models")?)?; let sources: Bound<'py, PyDict> = by_name(&project.getattr("sources")?)?; @@ -49,7 +47,7 @@ pub(crate) fn write_contracts<'py>( if evidence.getattr("resource_type")?.eq(&model_type)? { let model: Bound<'py, PyAny> = models.as_any().get_item(&name)?; let edited: Bound<'py, PyAny> = call_with( - &native.getattr("edit_contract_model")?, + ®istry::native(py, NativeAttr::EditContractModel)?, &[ ("model", model.clone()), ("physical_columns", physical_columns), @@ -65,7 +63,7 @@ pub(crate) fn write_contracts<'py>( let path: Bound<'py, PyAny> = source.getattr("source_file")?.getattr("file_path")?; let edited: Bound<'py, PyAny> = call_with( - &native.getattr("edit_contract_source")?, + ®istry::native(py, NativeAttr::EditContractSource)?, &[ ("source", source), ("physical_columns", physical_columns), @@ -80,7 +78,7 @@ pub(crate) fn write_contracts<'py>( updates.set_item(&path, contents)?; } let remaining: Bound<'py, PyAny> = call_with( - &native.getattr("remaining_contract_findings")?, + ®istry::native(py, NativeAttr::RemainingContractFindings)?, &[ ("findings", findings), ("overwrite", write.overwrite.clone()), @@ -128,10 +126,9 @@ fn applied<'py>( updates: &Bound<'py, PyDict>, ) -> PyResult<()> { let py: Python<'py> = result.py(); - let native = py.import(NATIVE_MODULE)?; write_all(updates)?; let discovered: Bound<'py, PyAny> = call_with( - &native.getattr("discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[("project_dir", write.project_dir.clone())], )?; if write.adapter.is_none() { @@ -140,10 +137,9 @@ fn applied<'py>( let keywords: Bound<'py, PyDict> = PyDict::new(py); keywords.set_item("no_cache", true)?; let selection: Bound<'py, PyAny> = - imported(py, NATIVE_RECORDS_MODULE, "CompileAnalysisSelection")? - .call((), Some(&keywords))?; + registry::class(py, RecordId::CompileAnalysisSelection)?.call((), Some(&keywords))?; call_with( - &native.getattr("build_compiled_project")?, + ®istry::native(py, NativeAttr::BuildCompiledProject)?, &[ ("discovered_inputs", discovered), ("adapter", write.adapter.clone()), @@ -159,7 +155,7 @@ fn applied<'py>( /// Write each `path: contents` pair atomically, in order. fn write_all(contents_by_path: &Bound<'_, PyDict>) -> PyResult<()> { let py: Python<'_> = contents_by_path.py(); - let write_atomically: Bound<'_, PyAny> = imported(py, NATIVE_MODULE, "write_atomically")?; + let write_atomically: Bound<'_, PyAny> = registry::native(py, NativeAttr::WriteAtomically)?; for (path, contents) in contents_by_path.iter() { call_with(&write_atomically, &[("path", path), ("contents", contents)])?; } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/contract_records/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/contract_records/constants.rs index 4b9a90b52a..59205d3677 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/contract_records/constants.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/contract_records/constants.rs @@ -1,5 +1,6 @@ //! Layouts of the project configuration and contract value records hosted natively. +use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::records::models::{FieldDefault, FieldSpec, RecordSpec}; const R_CLONE_POLICY: RecordSpec = RecordSpec { @@ -40,15 +41,15 @@ const R_MATERIALIZATION_DEFAULTS_CONFIG: RecordSpec = RecordSpec { fields: &[ FieldSpec::new( "table", - FieldDefault::Call("sqlbuild._native", "MaterializationRetentionDefaults"), + FieldDefault::NativeCall(RecordId::MaterializationRetentionDefaults), ), FieldSpec::new( "incremental", - FieldDefault::Call("sqlbuild._native", "MaterializationRetentionDefaults"), + FieldDefault::NativeCall(RecordId::MaterializationRetentionDefaults), ), FieldSpec::new( "snapshot", - FieldDefault::Call("sqlbuild._native", "MaterializationRetentionDefaults"), + FieldDefault::NativeCall(RecordId::MaterializationRetentionDefaults), ), ], }; @@ -67,11 +68,11 @@ const R_RESOLVED_TABLE_TYPE: RecordSpec = RecordSpec { fields: &[ FieldSpec::new( "value", - FieldDefault::Member("sqlbuild._native", "TableType", "TRANSIENT"), + FieldDefault::NativeMember(RecordId::TableType, "TRANSIENT"), ), FieldSpec::new( "source", - FieldDefault::Member("sqlbuild._native", "TableTypeSource", "DEFAULT"), + FieldDefault::NativeMember(RecordId::TableTypeSource, "DEFAULT"), ), FieldSpec::new("declared", FieldDefault::False), ], @@ -111,10 +112,7 @@ const R_TARGET_CONFIG: RecordSpec = RecordSpec { FieldSpec::new("loader_schema", FieldDefault::None), FieldSpec::new("defer_sources_to", FieldDefault::None), FieldSpec::new("defer_clone_from", FieldDefault::None), - FieldSpec::new( - "clone", - FieldDefault::Call("sqlbuild._native", "ClonePolicy"), - ), + FieldSpec::new("clone", FieldDefault::NativeCall(RecordId::ClonePolicy)), FieldSpec::new("compile_cache", FieldDefault::None), FieldSpec::new("time_travel_retention", FieldDefault::None), FieldSpec::new( @@ -125,31 +123,24 @@ const R_TARGET_CONFIG: RecordSpec = RecordSpec { FieldSpec::new("default_table_type", FieldDefault::None), FieldSpec::new( "table_type_downgrade", - FieldDefault::Member( - "sqlbuild._native", - "TableTypeDowngradePolicy", - "REQUIRE_CONFIRMATION", - ), + FieldDefault::NativeMember(RecordId::TableTypeDowngradePolicy, "REQUIRE_CONFIRMATION"), ), FieldSpec::new( "time_travel_retention_decrease", - FieldDefault::Member("sqlbuild._native", "RetentionDecreasePolicy", "DENY"), + FieldDefault::NativeMember(RecordId::RetentionDecreasePolicy, "DENY"), ), FieldSpec::new( "missing_migration_origin", - FieldDefault::Member("sqlbuild._native", "MissingMigrationOriginPolicy", "ALLOW"), + FieldDefault::NativeMember(RecordId::MissingMigrationOriginPolicy, "ALLOW"), ), FieldSpec::new( "execution_limits", - FieldDefault::Call("sqlbuild._native", "ExecutionLimitsConfig"), - ), - FieldSpec::new( - "diff", - FieldDefault::Call("sqlbuild._native", "TargetDiffConfig"), + FieldDefault::NativeCall(RecordId::ExecutionLimitsConfig), ), + FieldSpec::new("diff", FieldDefault::NativeCall(RecordId::TargetDiffConfig)), FieldSpec::new( "warehouses", - FieldDefault::Call("sqlbuild._native", "TargetWarehousesConfig"), + FieldDefault::NativeCall(RecordId::TargetWarehousesConfig), ), ], }; @@ -167,7 +158,7 @@ const R_LOCAL_TARGET_CONFIG: RecordSpec = RecordSpec { FieldSpec::new("defer_clone_from", FieldDefault::None), FieldSpec::new( "clone", - FieldDefault::Call("sqlbuild._native", "LocalClonePolicy"), + FieldDefault::NativeCall(RecordId::LocalClonePolicy), ), FieldSpec::new("compile_cache", FieldDefault::None), FieldSpec::new("time_travel_retention", FieldDefault::None), @@ -182,15 +173,12 @@ const R_LOCAL_TARGET_CONFIG: RecordSpec = RecordSpec { FieldSpec::new("missing_migration_origin", FieldDefault::None), FieldSpec::new( "execution_limits", - FieldDefault::Call("sqlbuild._native", "ExecutionLimitsConfig"), - ), - FieldSpec::new( - "diff", - FieldDefault::Call("sqlbuild._native", "TargetDiffConfig"), + FieldDefault::NativeCall(RecordId::ExecutionLimitsConfig), ), + FieldSpec::new("diff", FieldDefault::NativeCall(RecordId::TargetDiffConfig)), FieldSpec::new( "warehouses", - FieldDefault::Call("sqlbuild._native", "TargetWarehousesConfig"), + FieldDefault::NativeCall(RecordId::TargetWarehousesConfig), ), ], }; @@ -203,7 +191,7 @@ const R_SETTINGS_CONFIG: RecordSpec = RecordSpec { FieldSpec::new("query_change_tracking", FieldDefault::True), FieldSpec::new( "column_contract_mode", - FieldDefault::Member("sqlbuild._native", "ColumnContractMode", "IMPLICIT"), + FieldDefault::NativeMember(RecordId::ColumnContractMode, "IMPLICIT"), ), FieldSpec::new("concurrency", FieldDefault::Int(1)), FieldSpec::new("auto_load_sources", FieldDefault::True), @@ -243,7 +231,7 @@ const R_CONSTANTS_CONFIG: RecordSpec = RecordSpec { doc: "Project-wide constant rendering configuration.", fields: &[FieldSpec::new( "collection_rendering", - FieldDefault::Member("sqlbuild._native", "CollectionRendering", "VALUE_LIST"), + FieldDefault::NativeMember(RecordId::CollectionRendering, "VALUE_LIST"), )], }; const R_FUTURE_CURSORS_CONFIG: RecordSpec = RecordSpec { @@ -253,7 +241,7 @@ const R_FUTURE_CURSORS_CONFIG: RecordSpec = RecordSpec { FieldSpec::new("max_distance", FieldDefault::None), FieldSpec::new( "action", - FieldDefault::Member("sqlbuild._native", "FutureCursorAction", "ERROR"), + FieldDefault::NativeMember(RecordId::FutureCursorAction, "ERROR"), ), ], }; @@ -264,7 +252,7 @@ const R_START_CURSORS_CONFIG: RecordSpec = RecordSpec { FieldSpec::new("max_ahead", FieldDefault::None), FieldSpec::new( "action", - FieldDefault::Member("sqlbuild._native", "FutureCursorAction", "ERROR"), + FieldDefault::NativeMember(RecordId::FutureCursorAction, "ERROR"), ), ], }; @@ -274,11 +262,11 @@ const R_CURSORS_CONFIG: RecordSpec = RecordSpec { fields: &[ FieldSpec::new( "future", - FieldDefault::Call("sqlbuild._native", "FutureCursorsConfig"), + FieldDefault::NativeCall(RecordId::FutureCursorsConfig), ), FieldSpec::new( "start", - FieldDefault::Call("sqlbuild._native", "StartCursorsConfig"), + FieldDefault::NativeCall(RecordId::StartCursorsConfig), ), ], }; @@ -289,7 +277,7 @@ const R_MICROBATCH_LIMITS_CONFIG: RecordSpec = RecordSpec { FieldSpec::new("max_batches", FieldDefault::None), FieldSpec::new( "action", - FieldDefault::Member("sqlbuild._native", "MicrobatchLimitAction", "ERROR"), + FieldDefault::NativeMember(RecordId::MicrobatchLimitAction, "ERROR"), ), ], }; @@ -298,7 +286,7 @@ const R_MICROBATCHES_CONFIG: RecordSpec = RecordSpec { doc: "Project-wide microbatch configuration.", fields: &[FieldSpec::new( "limits", - FieldDefault::Call("sqlbuild._native", "MicrobatchLimitsConfig"), + FieldDefault::NativeCall(RecordId::MicrobatchLimitsConfig), )], }; const R_DEFAULTS_CONFIG: RecordSpec = RecordSpec { @@ -400,7 +388,7 @@ const R_SCENARIO_CONFIG: RecordSpec = RecordSpec { FieldSpec::new("local_type_overrides", FieldDefault::EmptyDict), FieldSpec::new( "snapshot_limits", - FieldDefault::Call("sqlbuild._native", "ScenarioSnapshotLimitsConfig"), + FieldDefault::NativeCall(RecordId::ScenarioSnapshotLimitsConfig), ), ], }; @@ -437,7 +425,7 @@ const R_LIFECYCLE_EVENT_SINKS_CONFIG: RecordSpec = RecordSpec { fields: &[ FieldSpec::new( "defaults", - FieldDefault::Call("sqlbuild._native", "LifecycleEventSinkFilterConfig"), + FieldDefault::NativeCall(RecordId::LifecycleEventSinkFilterConfig), ), FieldSpec::new("named", FieldDefault::EmptyDict), FieldSpec::new("shutdown_timeout", FieldDefault::None), @@ -449,7 +437,7 @@ const R_SINKS_CONFIG: RecordSpec = RecordSpec { doc: "Typed destination sink configuration.", fields: &[FieldSpec::new( "lifecycle", - FieldDefault::Call("sqlbuild._native", "LifecycleEventSinksConfig"), + FieldDefault::NativeCall(RecordId::LifecycleEventSinksConfig), )], }; const R_PROJECT_CONFIG: RecordSpec = RecordSpec { @@ -463,62 +451,50 @@ const R_PROJECT_CONFIG: RecordSpec = RecordSpec { FieldSpec::new("connections", FieldDefault::EmptyDict), FieldSpec::new( "settings", - FieldDefault::Call("sqlbuild._native", "SettingsConfig"), - ), - FieldSpec::new( - "scopes", - FieldDefault::Call("sqlbuild._native", "ScopesConfig"), + FieldDefault::NativeCall(RecordId::SettingsConfig), ), + FieldSpec::new("scopes", FieldDefault::NativeCall(RecordId::ScopesConfig)), FieldSpec::new( "references", - FieldDefault::Call("sqlbuild._native", "ReferencesConfig"), + FieldDefault::NativeCall(RecordId::ReferencesConfig), ), - FieldSpec::new("cost", FieldDefault::Call("sqlbuild._native", "CostConfig")), + FieldSpec::new("cost", FieldDefault::NativeCall(RecordId::CostConfig)), FieldSpec::new( "constants", - FieldDefault::Call("sqlbuild._native", "ConstantsConfig"), - ), - FieldSpec::new( - "cursors", - FieldDefault::Call("sqlbuild._native", "CursorsConfig"), + FieldDefault::NativeCall(RecordId::ConstantsConfig), ), + FieldSpec::new("cursors", FieldDefault::NativeCall(RecordId::CursorsConfig)), FieldSpec::new( "microbatches", - FieldDefault::Call("sqlbuild._native", "MicrobatchesConfig"), + FieldDefault::NativeCall(RecordId::MicrobatchesConfig), ), FieldSpec::new( "defaults", - FieldDefault::Call("sqlbuild._native", "DefaultsConfig"), + FieldDefault::NativeCall(RecordId::DefaultsConfig), ), - FieldSpec::new("diff", FieldDefault::Call("sqlbuild._native", "DiffConfig")), + FieldSpec::new("diff", FieldDefault::NativeCall(RecordId::DiffConfig)), FieldSpec::new( "materialization_defaults", - FieldDefault::Call("sqlbuild._native", "MaterializationDefaultsConfig"), + FieldDefault::NativeCall(RecordId::MaterializationDefaultsConfig), ), FieldSpec::new("path_defaults", FieldDefault::EmptyDict), FieldSpec::new("vars", FieldDefault::EmptyDict), FieldSpec::new("targets", FieldDefault::EmptyDict), - FieldSpec::new( - "janitor", - FieldDefault::Call("sqlbuild._native", "JanitorConfig"), - ), + FieldSpec::new("janitor", FieldDefault::NativeCall(RecordId::JanitorConfig)), FieldSpec::new( "migrations", - FieldDefault::Call("sqlbuild._native", "MigrationsConfig"), + FieldDefault::NativeCall(RecordId::MigrationsConfig), ), FieldSpec::new( "snapshots", - FieldDefault::Call("sqlbuild._native", "SnapshotsConfig"), + FieldDefault::NativeCall(RecordId::SnapshotsConfig), ), FieldSpec::new( "scenario", - FieldDefault::Call("sqlbuild._native", "ScenarioConfig"), - ), - FieldSpec::new("dbt", FieldDefault::Call("sqlbuild._native", "DbtConfig")), - FieldSpec::new( - "sinks", - FieldDefault::Call("sqlbuild._native", "SinksConfig"), + FieldDefault::NativeCall(RecordId::ScenarioConfig), ), + FieldSpec::new("dbt", FieldDefault::NativeCall(RecordId::DbtConfig)), + FieldSpec::new("sinks", FieldDefault::NativeCall(RecordId::SinksConfig)), ], }; const R_LOCAL_CONFIG: RecordSpec = RecordSpec { @@ -532,17 +508,14 @@ const R_LOCAL_CONFIG: RecordSpec = RecordSpec { FieldSpec::new("targets", FieldDefault::EmptyDict), FieldSpec::new( "settings", - FieldDefault::Call("sqlbuild._native", "SettingsConfig"), + FieldDefault::NativeCall(RecordId::SettingsConfig), ), FieldSpec::new("setting_overrides", FieldDefault::EmptyFrozenset), FieldSpec::new("vars", FieldDefault::EmptyDict), - FieldSpec::new( - "dbt", - FieldDefault::Call("sqlbuild._native", "LocalDbtConfig"), - ), + FieldSpec::new("dbt", FieldDefault::NativeCall(RecordId::LocalDbtConfig)), FieldSpec::new( "scenario", - FieldDefault::Call("sqlbuild._native", "ScenarioConfig"), + FieldDefault::NativeCall(RecordId::ScenarioConfig), ), ], }; @@ -646,7 +619,7 @@ const R_SCHEMA_SEED_ENTRY: RecordSpec = RecordSpec { FieldSpec::new("meta", FieldDefault::EmptyDict), FieldSpec::new( "csv_settings", - FieldDefault::Call("sqlbuild._native", "SeedCsvSettings"), + FieldDefault::NativeCall(RecordId::SeedCsvSettings), ), FieldSpec::new("columns", FieldDefault::EmptyTuple), FieldSpec::new("tags", FieldDefault::EmptyTuple), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/contracts/contract_diagnostics.rs b/crates/sqlbuild-python/src/bindings/_helpers/contracts/contract_diagnostics.rs index 5de21e3b7c..79b95cfef9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/contracts/contract_diagnostics.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/contracts/contract_diagnostics.rs @@ -11,15 +11,11 @@ use crate::bindings::_helpers::compiled_project::project::NativeCompiledProject; use crate::bindings::_helpers::contracts::model_contracts::{ ConflictRow, DiagnosticRow, evaluate_project_model_contracts, project_promotion_conflicts, }; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sqltext::model_headers::{ColumnLocation, location_rows}; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; -const CONTRACT_MODELS_MODULE: &str = "sqlbuild._native"; -const SPEC_MODELS_MODULE: &str = "sqlbuild._native"; const PATHLIB_MODULE: &str = "pathlib"; const IMPLICIT_COLUMN_CONTRACTS: &str = "implicit"; const GENERIC_DIALECT: &str = "generic"; @@ -40,11 +36,7 @@ fn keyword_call<'py>( /// The compile's retained native project, read through `_native.compiled_project_facts`. fn native_project<'py>(project: &Bound<'py, PyAny>) -> PyResult> { - project - .py() - .import(NATIVE_MODULE)? - .getattr("compiled_project_facts")? - .call1((project,)) + registry::native(project.py(), NativeAttr::CompiledProjectFacts)?.call1((project,)) } /// Where the model's SQL produces `column_name`, recovered from the authored SQL when needed. @@ -73,7 +65,7 @@ fn output_column_location<'py>( }; let py: Python<'py> = model.py(); let location: Bound<'py, PyAny> = keyword_call( - &py.import(SPEC_MODELS_MODULE)?.getattr("SourceLocation")?, + ®istry::class(py, RecordId::SourceLocation)?, vec![ ("path", model.getattr("relative_path")?), ("line", line.into_pyobject(py)?.into_any()), @@ -120,8 +112,7 @@ fn related_locations<'py>( return Ok(PyTuple::empty(py)); }; let related: Bound<'py, PyAny> = keyword_call( - &py.import(NATIVE_RECORDS_MODULE)? - .getattr("RelatedLocation")?, + ®istry::class(py, RecordId::RelatedLocation)?, vec![ ("label", OUTPUT_LABEL.into_pyobject(py)?.into_any()), ("location", location), @@ -139,8 +130,7 @@ fn contract_diagnostic<'py>( let (code, is_error, message, column_name, declared_index, output_column, related, help) = row; let severity: &str = if is_error { "error" } else { "warning" }; keyword_call( - &py.import(COMPILE_MODELS_MODULE)? - .getattr("CompilerDiagnostic")?, + ®istry::class(py, RecordId::CompilerDiagnostic)?, vec![ ("phase", CONTRACT_PHASE.into_pyobject(py)?.into_any()), ("severity", severity.into_pyobject(py)?.into_any()), @@ -207,8 +197,7 @@ fn evaluate_model_contracts<'py>( } } keyword_call( - &py.import(CONTRACT_MODELS_MODULE)? - .getattr("ContractValidationResult")?, + ®istry::class(py, RecordId::ContractValidationResult)?, vec![("diagnostics", PyTuple::new(py, diagnostics)?.into_any())], ) } @@ -237,9 +226,7 @@ fn promotion_conflict_diagnostics<'py>( ), &native, )?; - let diagnostic_type: Bound<'py, PyAny> = py - .import(COMPILE_MODELS_MODULE)? - .getattr("CompilerDiagnostic")?; + let diagnostic_type: Bound<'py, PyAny> = registry::class(py, RecordId::CompilerDiagnostic)?; let path_type: Bound<'py, PyAny> = py.import(PATHLIB_MODULE)?.getattr("Path")?; let mut diagnostics: Vec> = Vec::with_capacity(conflicts.len()); for (name, relative_path, code, message, help) in conflicts { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/contracts/project_config.rs b/crates/sqlbuild-python/src/bindings/_helpers/contracts/project_config.rs index b2a1074e97..7370c4c364 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/contracts/project_config.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/contracts/project_config.rs @@ -16,30 +16,19 @@ use sqlbuild_config::project::main::resolve_config_file::resolve_config_file; use sqlbuild_config::project::models::{Built, ConfigFile, ConfigPaths}; use crate::bindings::_helpers::boundary::config_values::config_value_to_python; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; use sqlbuild_core::constants::PROJECT_CONFIG_FILENAME; -const MODELS_MODULE: &str = "sqlbuild._native"; -const ERRORS_MODULE: &str = "sqlbuild._native"; -const PROJECT_CONFIG_ERROR: &str = "ProjectConfigError"; const FILE_NAMES: [&str; 4] = [ PROJECT_CONFIG_FILENAME, "sqlbuild_project.yml", "sqlbuild_local.toml", "sqlbuild_local.yml", ]; -/// The module of each `StrEnum` a loaded config holds. -const ENUM_MODULES: [(&str, &str); 10] = [ - ("ColumnContractMode", "sqlbuild._native"), - ("FutureCursorAction", "sqlbuild._native"), - ("MicrobatchLimitAction", "sqlbuild._native"), - ("TableType", "sqlbuild._native"), - ("TableTypeDowngradePolicy", "sqlbuild._native"), - ("RetentionDecreasePolicy", "sqlbuild._native"), - ("MissingMigrationOriginPolicy", "sqlbuild._native"), - ("EventExportSeverity", "sqlbuild._native"), - ("AuditSeverity", "sqlbuild.compiler.auditing.types"), - ("CollectionRendering", "sqlbuild._native"), -]; +/// The Python module of each `StrEnum` a loaded config holds that is not native. +const PYTHON_ENUM_MODULES: [(&str, &str); 1] = + [("AuditSeverity", "sqlbuild.compiler.auditing.types")]; /// The four configuration file paths of a project directory, as Python and native paths. struct Files<'py> { @@ -95,10 +84,7 @@ fn config_error(py: Python<'_>, failure: &ConfigFailure, files: &Files<'_>) -> P } let keywords = PyDict::new(py); keywords.set_item("help", failure.help.as_deref())?; - let error = py - .import(ERRORS_MODULE)? - .getattr(PROJECT_CONFIG_ERROR)? - .call((message,), Some(&keywords))?; + let error = registry::error(py, ErrorId::ProjectConfig)?.call((message,), Some(&keywords))?; Ok(PyErr::from_value(error)) } @@ -126,11 +112,12 @@ fn load_error(py: Python<'_>, failure: &LoadFailure, files: &Files<'_>) -> PyRes } fn enum_member<'py>(py: Python<'py>, class: &str, value: &str) -> PyResult> { - let module = ENUM_MODULES - .iter() - .find(|(name, _)| *name == class) - .map_or("sqlbuild._native", |(_, module)| *module); - py.import(module)?.getattr(class)?.call1((value,)) + let class_object: Bound<'py, PyAny> = + match PYTHON_ENUM_MODULES.iter().find(|(name, _)| *name == class) { + Some((_, module)) => py.import(*module)?.getattr(class)?, + None => registry::class(py, registry::record_named(class)?)?, + }; + class_object.call1((value,)) } fn object<'py>( @@ -142,9 +129,7 @@ fn object<'py>( for (name, value) in fields { keywords.set_item(name, built(py, value)?)?; } - py.import(MODELS_MODULE)? - .getattr(class)? - .call((), Some(&keywords)) + registry::class(py, registry::record_named(class)?)?.call((), Some(&keywords)) } fn built(py: Python<'_>, value: Built) -> PyResult> { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/contracts/resource_identities.rs b/crates/sqlbuild-python/src/bindings/_helpers/contracts/resource_identities.rs index 0df7568b94..f19c8b5671 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/contracts/resource_identities.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/contracts/resource_identities.rs @@ -1,5 +1,7 @@ //! Canonical snake_case resource identity checks and SQL function node types. +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{ Bound, PyAny, PyAnyMethods, PyErr, PyModule, PyModuleMethods, PyResult, Python, }; @@ -7,7 +9,6 @@ use pyo3::types::{PyDict, PyString, PyStringMethods}; use pyo3::{pyfunction, wrap_pyfunction}; use sqlbuild_model_config::resource_identities::main::resource_identity_failure::resource_identity_failure; -const RESOURCE_NAME_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; const TABLE_FUNCTION_NODE_TYPE: &str = "table_fn"; const UDF_NODE_TYPE: &str = "udf"; @@ -32,10 +33,8 @@ fn validate_resource_identity( ); let keywords: Bound<'_, PyDict> = PyDict::new(py); keywords.set_item("help", help_text)?; - let error: Bound<'_, PyAny> = py - .import(RESOURCE_NAME_EXCEPTIONS_MODULE)? - .getattr("ResourceIdentityError")? - .call((message,), Some(&keywords))?; + let error: Bound<'_, PyAny> = + registry::error(py, ErrorId::ResourceIdentity)?.call((message,), Some(&keywords))?; Err(PyErr::from_value(error)) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/contracts/spec_resolution.rs b/crates/sqlbuild-python/src/bindings/_helpers/contracts/spec_resolution.rs index 32feaabe89..dd1c27fc9f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/contracts/spec_resolution.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/contracts/spec_resolution.rs @@ -12,18 +12,18 @@ use pyo3::{pyfunction, wrap_pyfunction}; use sqlbuild_model_config::name_patterns::main::compile_name_pattern::compile_name_pattern; use sqlbuild_model_config::name_patterns::models::NamePattern; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::contracts::effective_config::{ effective_config_target_fields, effective_config_target_name, }; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; -const MODELS_MODULE: &str = "sqlbuild._native"; -const ERRORS_MODULE: &str = "sqlbuild._native"; -const TARGET_GROUPS: [(&str, &str); 4] = [ - ("clone", "ClonePolicy"), - ("execution_limits", "ExecutionLimitsConfig"), - ("diff", "TargetDiffConfig"), - ("warehouses", "TargetWarehousesConfig"), +const TARGET_GROUPS: [(&str, RecordId); 4] = [ + ("clone", RecordId::ClonePolicy), + ("execution_limits", RecordId::ExecutionLimitsConfig), + ("diff", RecordId::TargetDiffConfig), + ("warehouses", RecordId::TargetWarehousesConfig), ]; const SNAPSHOT_LIMITS: [&str; 4] = [ "max_rows_per_relation", @@ -42,7 +42,7 @@ thread_local! { fn spec_error<'py>(py: Python<'py>, message: Bound<'py, PyString>) -> PyResult { Ok(PyErr::from_value( - imported(py, ERRORS_MODULE, "SpecConfigError")?.call1((message,))?, + registry::error(py, ErrorId::SpecConfig)?.call1((message,))?, )) } @@ -72,7 +72,7 @@ pub(crate) fn resolve_target_config<'py>( local_config: &Bound<'py, PyAny>, target_name: &Bound<'py, PyAny>, ) -> PyResult> { - let target_class: Bound<'py, PyAny> = imported(py, MODELS_MODULE, "TargetConfig")?; + let target_class: Bound<'py, PyAny> = registry::class(py, RecordId::TargetConfig)?; let (rows, error) = effective_config_target_fields( py, (project_config.clone(), local_config.clone()), @@ -99,8 +99,7 @@ pub(crate) fn resolve_target_config<'py>( } } for ((group, class_name), (_, fields)) in TARGET_GROUPS.iter().zip(&grouped) { - let nested: Bound<'py, PyAny> = - imported(py, MODELS_MODULE, class_name)?.call((), Some(fields))?; + let nested: Bound<'py, PyAny> = registry::class(py, *class_name)?.call((), Some(fields))?; values.set_item(*group, nested)?; } target_class.call((), Some(&values)) @@ -184,7 +183,7 @@ fn resolve_effective_scenario_config<'py>( }) .collect::>()?; call_with( - &imported(py, MODELS_MODULE, "ScenarioConfig")?, + ®istry::class(py, RecordId::ScenarioConfig)?, &[ ( "run_namespace", @@ -197,7 +196,7 @@ fn resolve_effective_scenario_config<'py>( ( "snapshot_limits", call_with( - &imported(py, MODELS_MODULE, "ScenarioSnapshotLimitsConfig")?, + ®istry::class(py, RecordId::ScenarioSnapshotLimitsConfig)?, &limits, )?, ), @@ -282,7 +281,7 @@ fn loader_parts<'py>( ), _ => (text(parts[0]), text(parts[1]), text(parts[2])), }; - imported(py, MODELS_MODULE, "LoaderDestinationParts")?.call1((database, schema, table)) + registry::class(py, RecordId::LoaderDestinationParts)?.call1((database, schema, table)) } /// Whether a family's optional `name_pattern` accepts `column_name`. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/contracts/sql_values.rs b/crates/sqlbuild-python/src/bindings/_helpers/contracts/sql_values.rs index f03670b956..6bde63f1ba 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/contracts/sql_values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/contracts/sql_values.rs @@ -1,5 +1,7 @@ //! Typed SQL value normalization between Python authored values and native `SqlValue`s. +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{ Bound, PyAny, PyAnyMethods, PyErr, PyModule, PyModuleMethods, PyResult, Python, }; @@ -13,10 +15,6 @@ use sqlbuild_model_config::sql_values::errors::SqlValueFailure; use sqlbuild_model_config::sql_values::main::normalize_sql_value::normalize_sql_value as normalized; use sqlbuild_model_config::sql_values::models::{RawKey, RawSqlValue, SqlValueLimits}; -const MODELS_MODULE: &str = "sqlbuild._native"; -const TYPES_MODULE: &str = "sqlbuild._native"; -const NATIVE_MODULE: &str = "sqlbuild._native"; - /// The Python classes authored values are told apart by and normalized values are built from. pub(crate) struct SqlValueClasses<'py> { decimal: Bound<'py, PyAny>, @@ -29,15 +27,13 @@ pub(crate) struct SqlValueClasses<'py> { impl<'py> SqlValueClasses<'py> { pub(crate) fn load(py: Python<'py>) -> PyResult { - let models = py.import(MODELS_MODULE)?; - let native = py.import(NATIVE_MODULE)?; Ok(Self { decimal: py.import("decimal")?.getattr("Decimal")?, - authored_set: native.getattr("AuthoredSqlSet")?, - value_call: native.getattr("AuthoredSqlValueCall")?, - sql_value: models.getattr("SqlValue")?, - logical_type: models.getattr("SqlLogicalType")?, - kind: py.import(TYPES_MODULE)?.getattr("SqlValueKind")?, + authored_set: registry::class(py, RecordId::AuthoredSqlSet)?, + value_call: registry::class(py, RecordId::AuthoredSqlValueCall)?, + sql_value: registry::class(py, RecordId::SqlValue)?, + logical_type: registry::class(py, RecordId::SqlLogicalType)?, + kind: registry::class(py, RecordId::SqlValueKind)?, }) } } @@ -178,9 +174,7 @@ pub(crate) fn sql_value<'py>( pub(crate) fn sql_value_error(py: Python<'_>, failure: SqlValueFailure) -> PyResult { Ok(match failure { SqlValueFailure::Invalid(message) => PyErr::from_value( - py.import(NATIVE_MODULE)? - .getattr("SqlValueValidationError")? - .call1((message,))?, + registry::native(py, NativeAttr::SqlValueValidationError)?.call1((message,))?, ), SqlValueFailure::DecimalOverflow => { let overflow = py.import("decimal")?.getattr("Overflow")?; @@ -204,10 +198,7 @@ fn normalize_sql_value<'py>( let raw = self::raw_value(raw_value, &classes)?; let applied: Bound<'py, PyAny> = match limits { Some(limits) if !limits.is_none() => limits.clone(), - _ => py - .import(NATIVE_MODULE)? - .getattr("SqlValueLimits")? - .call0()?, + _ => registry::class(py, RecordId::SqlValueLimits)?.call0()?, }; let limits = SqlValueLimits { max_depth: applied.getattr("max_depth")?.extract()?, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/contracts/state_literals.rs b/crates/sqlbuild-python/src/bindings/_helpers/contracts/state_literals.rs index e8cc03cc1d..cd02eb1c20 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/contracts/state_literals.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/contracts/state_literals.rs @@ -7,13 +7,14 @@ use pyo3::prelude::{ use pyo3::types::{PyBool, PyDict, PyInt, PyString}; use pyo3::{pyfunction, wrap_pyfunction}; -const EXCEPTIONS_MODULE: &str = "sqlbuild.sql_values.exceptions"; const DATETIME_MODULE: &str = "datetime"; const JSON_MODULE: &str = "json"; use sqlbuild_state::freshness::constants::STRING_STATE_VALUE_TYPE as STRING_TYPE; const INTEGER_TYPE: &str = "integer"; const BOOLEAN_TYPE: &str = "boolean"; const TIMESTAMP_TYPE: &str = "timestamp"; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use sqlbuild_state::freshness::constants::TEXT_TIMESTAMP_STATE_VALUE_TYPE as TEXT_TIMESTAMP_TYPE; const DATE_TYPE: &str = "date"; const JSON_TYPE: &str = "json"; @@ -144,10 +145,8 @@ fn typed_null(declared_type: &str) -> &'static str { fn rendering_error(py: Python<'_>, message: &str) -> PyErr { let raised = || -> PyResult { - let error: Bound<'_, PyAny> = py - .import(EXCEPTIONS_MODULE)? - .getattr("SqlValueRenderingError")? - .call1((message,))?; + let error: Bound<'_, PyAny> = + host::entry(py, ExecutorHost::SqlValueRenderingError)?.call1((message,))?; Ok(PyErr::from_value(error)) }; raised().unwrap_or_else(|error| error) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cost_command/bounds.rs b/crates/sqlbuild-python/src/bindings/_helpers/cost_command/bounds.rs index 1e961676eb..5192cf7e9f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_command/bounds.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_command/bounds.rs @@ -12,9 +12,10 @@ use sqlbuild_core::cost_command::main::is_zero_cost_duration::is_zero_cost_durat use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::cost_command::refusal::cost_error; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; const DATETIME_MODULE: &str = "datetime"; -const DURATION_MODELS: &str = "sqlbuild._native"; const MINUTE_TO_DAY_DURATION_UNITS: [&str; 3] = ["m", "h", "d"]; fn utc(py: Python<'_>) -> PyResult> { @@ -101,7 +102,7 @@ pub(crate) fn parse_since<'py>(py: Python<'py>, value: &str) -> PyResult = - imported(py, DURATION_MODELS, "Duration")?.call_method1("parse", (value,))?; + registry::class(py, RecordId::Duration)?.call_method1("parse", (value,))?; if duration.is_none() { return parse_bound(py, value, "--since", false); } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cost_command/command.rs b/crates/sqlbuild-python/src/bindings/_helpers/cost_command/command.rs index 64d9d392b6..3d4cacdeb9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_command/command.rs @@ -18,8 +18,8 @@ use crate::bindings::_helpers::cost_command::refresh::refresh_pending_cost_run; use crate::bindings::_helpers::cost_command::refusal::cost_error; use crate::bindings::_helpers::cost_command::sorting::{sort_history, sort_resources}; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; - -const NATIVE_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; /// The request fields every cost view reads. struct CostRequest<'py> { @@ -103,7 +103,7 @@ fn refused(py: Python<'_>, result: Result<(), CostCommandError>) -> PyResult<()> fn cost_runs<'py>(request: &CostRequest<'py>) -> PyResult>> { let py: Python<'py> = request.request.py(); call_with( - &imported(py, NATIVE_MODULE, "list_cost_runs")?, + ®istry::native(py, NativeAttr::ListCostRuns)?, &[("project_dir", request.project_dir.clone())], )? .try_iter()? @@ -178,7 +178,7 @@ fn run_history(request: &CostRequest<'_>) -> PyResult { let records: Bound<'_, PyAny> = PyTuple::new(py, records)?.into_any(); let count: Bound<'_, PyAny> = PyInt::new(py, matching_count).into_any(); let payload: String = call_with( - &imported(py, NATIVE_MODULE, "format_cost_history_json")?, + ®istry::native(py, NativeAttr::FormatCostHistoryJson)?, &[ ("records", records.clone()), ("matching_count", count.clone()), @@ -187,7 +187,7 @@ fn run_history(request: &CostRequest<'_>) -> PyResult { .extract()?; request.emit(&payload, || { call_with( - &imported(py, NATIVE_MODULE, "format_cost_history")?, + ®istry::native(py, NativeAttr::FormatCostHistory)?, &[ ("records", records), ("matching_count", count), @@ -208,7 +208,7 @@ fn resolve_run<'py>(request: &CostRequest<'py>) -> PyResult> { let records: Vec> = cost_runs(request)?; if request.selector == LATEST_SELECTOR { let latest: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "resolve_cost_run")?, + ®istry::native(py, NativeAttr::ResolveCostRun)?, &[ ("project_dir", request.project_dir.clone()), ("selector", PyString::new(py, &request.selector).into_any()), @@ -279,7 +279,7 @@ fn run_detail(request: &CostRequest<'_>) -> PyResult { } None => record.clone(), }; - let payload: String = imported(py, NATIVE_MODULE, "format_cost_json")? + let payload: String = registry::native(py, NativeAttr::FormatCostJson)? .call1((json_record,))? .extract()?; request.emit(&payload, || { @@ -287,7 +287,7 @@ fn run_detail(request: &CostRequest<'_>) -> PyResult { options.set_item("record", &record)?; options.set_item("use_color", request.use_color()?)?; options.set_item("limit", limit)?; - imported(py, NATIVE_MODULE, "format_cost_breakdown")? + registry::native(py, NativeAttr::FormatCostBreakdown)? .call((), Some(&options))? .extract() })?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cost_command/refresh.rs b/crates/sqlbuild-python/src/bindings/_helpers/cost_command/refresh.rs index 0f3bdfdcdd..c4e0174cab 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_command/refresh.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_command/refresh.rs @@ -12,11 +12,10 @@ use sqlbuild_core::cost_command::models::CostCoverage; use crate::bindings::_helpers::command_runtime::adapters::resolve_cli_adapter; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; use crate::bindings::_helpers::cost_telemetry::values::cost_aware; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const EFFECTIVE_TARGET_MODULE: &str = "sqlbuild._native"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; -const COST_TYPES: &str = "sqlbuild.cost.types"; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; fn coverage(summary: &Bound<'_, PyAny>) -> PyResult { Ok(CostCoverage { @@ -40,7 +39,7 @@ fn refresh_record<'py>( let py: Python<'py> = project_dir.py(); let resolve = || { call_with( - &imported(py, NATIVE_MODULE, "resolve_cost_run")?, + ®istry::native(py, NativeAttr::ResolveCostRun)?, &[ ("project_dir", project_dir.clone()), ("selector", selector.clone()), @@ -51,7 +50,7 @@ fn refresh_record<'py>( return resolve(); } let exact: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "read_cost_run")?, + ®istry::native(py, NativeAttr::ReadCostRun)?, &[ ("project_dir", project_dir.clone()), ("run_id", selector.clone()), @@ -104,18 +103,18 @@ fn refresh(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult<()> { } let target_name: Bound<'_, PyAny> = record.getattr("target_name")?; let discovered: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[("project_dir", project_dir.clone())], )?; let target_config: Bound<'_, PyAny> = call_with( - &imported(py, EFFECTIVE_TARGET_MODULE, "build_effective_target_config")?, + ®istry::native(py, NativeAttr::BuildEffectiveTargetConfig)?, &[ ("discovered_inputs", discovered.clone()), ("selected_target", target_name.clone()), ], )?; let adapter_name: Bound<'_, PyAny> = call_with( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), @@ -126,7 +125,7 @@ fn refresh(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult<()> { return Ok(()); } let none_capability: Bound<'_, PyAny> = - imported(py, COST_TYPES, "CostCapability")?.getattr("NONE")?; + host::entry(py, ExecutorHost::CostCapability)?.getattr("NONE")?; if adapter .call_method0("cost_capability")? .eq(none_capability)? @@ -134,7 +133,7 @@ fn refresh(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult<()> { return Ok(()); } let connection_config: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "resolve_project_connection_config")?, + ®istry::native(py, NativeAttr::ResolveProjectConnectionConfig)?, &[ ("discovered_inputs", discovered), ("project_dir", project_dir.clone()), @@ -171,7 +170,7 @@ fn refresh(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult<()> { let refreshed: Bound<'_, PyAny> = imported(py, "dataclasses", "replace")?.call((record,), Some(&changes))?; call_with( - &imported(py, NATIVE_MODULE, "write_cost_run")?, + ®istry::native(py, NativeAttr::WriteCostRun)?, &[("project_dir", project_dir), ("record", refreshed)], )?; Ok(()) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cost_command/refusal.rs b/crates/sqlbuild-python/src/bindings/_helpers/cost_command/refusal.rs index 13b791cb68..72b2d7bd69 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_command/refusal.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_command/refusal.rs @@ -4,16 +4,15 @@ use pyo3::prelude::{Bound, PyAnyMethods, PyErr, Python}; use pyo3::types::{PyDict, PyDictMethods}; use sqlbuild_core::cost_command::errors::CostCommandError; -use crate::bindings::_helpers::compile_driver::python_calls::imported; - -const ERRORS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; /// The `CliUserError` for a refused cost request. pub(crate) fn cost_error(py: Python<'_>, error: &CostCommandError) -> PyErr { let raised = (|| { let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("code", error.code)?; - imported(py, ERRORS_MODULE, "CliUserError")?.call((error.message.as_str(),), Some(&options)) + registry::error(py, ErrorId::CliUser)?.call((error.message.as_str(),), Some(&options)) })(); match raised { Ok(error) => PyErr::from_value(error), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cost_report/degradation.rs b/crates/sqlbuild-python/src/bindings/_helpers/cost_report/degradation.rs index b3d7858934..2c5d62f342 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_report/degradation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_report/degradation.rs @@ -11,16 +11,17 @@ use pyo3::{pyfunction, wrap_pyfunction}; use sqlbuild_core::cost_report::main::format_cost_breakdown::format_cost_breakdown; use sqlbuild_core::cost_report::models::BreakdownOptions; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::cost_report::records::cost_run; use crate::bindings::_helpers::cost_report::store; use crate::bindings::_helpers::cost_telemetry::values::cost_aware; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS: &str = "sqlbuild._native"; const LOGGER_NAME: &str = "sqlbuild.cli.commands._helpers.cost.collection"; const COLLECTION_LOGGER_NAME: &str = "sqlbuild.cost"; -const COST_TYPES: &str = "sqlbuild.cost.types"; -const COST_MODELS: &str = "sqlbuild.cost.models"; const DEFAULT_RATE_SOURCE: &str = "default"; const CONFIGURED_RATE_SOURCE: &str = "configured"; const STATEMENT_LEDGER_PATH: [&str; 4] = ["target", "executions", "", "statements.jsonl"]; @@ -162,7 +163,7 @@ fn cost_record<'py>( "had_executable_work", finalization.getattr("had_executable_work")?, )); - call_with(&imported(py, NATIVE_RECORDS, "CostRunRecord")?, &fields) + call_with(®istry::class(py, RecordId::CostRunRecord)?, &fields) } fn summary<'py>( @@ -172,7 +173,7 @@ fn summary<'py>( message: Bound<'py, PyAny>, ) -> PyResult> { call_with( - &imported(py, COST_MODELS, "RunCostSummary")?, + &host::entry(py, ExecutorHost::RunCostSummary)?, &[ ("status", status), ("usd_per_credit", config.getattr("usd_per_credit")?), @@ -216,7 +217,7 @@ fn collection_failure<'py>( COLLECTION_LOGGER_NAME, )?; let status: Bound<'py, PyAny> = - imported(py, COST_TYPES, "CostStatus")?.getattr("COLLECTION_FAILED")?; + host::entry(py, ExecutorHost::CostStatus)?.getattr("COLLECTION_FAILED")?; let message: Bound<'py, PyAny> = PyString::new( py, &format!("Cost telemetry failed during {stage} ({error_type})."), @@ -286,7 +287,7 @@ pub(crate) fn finalize_build_cost<'py>( return collection_failure(finalization, rate, "capability detection", &error); } }; - if capability.eq(imported(py, COST_TYPES, "CostCapability")?.getattr("NONE")?)? { + if capability.eq(host::entry(py, ExecutorHost::CostCapability)?.getattr("NONE")?)? { return Ok(py.None().into_bound(py)); } let cost: Bound<'py, PyAny> = if finalization.getattr("collect")?.is_truthy()? { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cost_report/records.rs b/crates/sqlbuild-python/src/bindings/_helpers/cost_report/records.rs index 54d46b6fa2..0b276d5592 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_report/records.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_report/records.rs @@ -6,15 +6,14 @@ use sqlbuild_core::cost_report::errors::CostArtifactError; use sqlbuild_core::cost_report::models::{CostResource, CostRun, CostSummary}; use crate::bindings::_helpers::compile_driver::python_calls::imported; - -const NATIVE_RECORDS: &str = "sqlbuild._native"; -const COST_MODELS: &str = "sqlbuild.cost.models"; -const COST_TYPES: &str = "sqlbuild.cost.types"; -const COST_EXCEPTIONS: &str = "sqlbuild._native"; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; /// The `CostArtifactError` Python raised for the same artifact. pub(crate) fn artifact_error(py: Python<'_>, error: &CostArtifactError) -> PyErr { - match imported(py, COST_EXCEPTIONS, "CostArtifactError") + match registry::native(py, NativeAttr::CostArtifactError) .and_then(|class| class.call1((error.message.as_str(),))) { Ok(instance) => PyErr::from_value(instance), @@ -155,7 +154,7 @@ fn resource_object<'py>( builders.decimal(&resource.estimated_compute_credits)?, )?; fields.set_item("estimated_usd", builders.decimal(&resource.estimated_usd)?)?; - imported(py, NATIVE_RECORDS, "ResourceCost")?.call((), Some(&fields)) + registry::class(py, RecordId::ResourceCost)?.call((), Some(&fields)) } fn summary_object<'py>( @@ -170,7 +169,7 @@ fn summary_object<'py>( let fields: Bound<'py, PyDict> = PyDict::new(py); fields.set_item( "status", - imported(py, COST_TYPES, "CostStatus")?.call1((cost.status.as_str(),))?, + host::entry(py, ExecutorHost::CostStatus)?.call1((cost.status.as_str(),))?, )?; fields.set_item("usd_per_credit", builders.decimal(&cost.usd_per_credit)?)?; fields.set_item("rate_source", &cost.rate_source)?; @@ -194,7 +193,7 @@ fn summary_object<'py>( fields.set_item("method", &cost.method)?; fields.set_item("limitations", PyTuple::new(py, &cost.limitations)?)?; fields.set_item("message", &cost.message)?; - imported(py, COST_MODELS, "RunCostSummary")?.call((), Some(&fields)) + host::entry(py, ExecutorHost::RunCostSummary)?.call((), Some(&fields)) } /// A plain run as the `CostRunRecord` it was read from. @@ -216,7 +215,7 @@ pub(crate) fn record_object<'py>(py: Python<'py>, run: &CostRun) -> PyResult( ) -> PyResult> { let decimal = |value: &PyDecimal| python_decimal(py, value); call_with( - &imported(py, NATIVE_RECORDS, "ResourceCost")?, + ®istry::class(py, RecordId::ResourceCost)?, &[ ( "resource_type", @@ -115,7 +116,7 @@ pub(crate) fn summary_object<'py>( let mut fields: Vec<(&str, Bound<'py, PyAny>)> = vec![ ( "status", - imported(py, COST_TYPES, "CostStatus")?.getattr(state.status)?, + host::entry(py, ExecutorHost::CostStatus)?.getattr(state.status)?, ), ("usd_per_credit", usd_per_credit.clone()), ("rate_source", rate_source.clone()), @@ -156,7 +157,7 @@ pub(crate) fn summary_object<'py>( ), ]); } - call_with(&imported(py, COST_MODELS, "RunCostSummary")?, &fields) + call_with(&host::entry(py, ExecutorHost::RunCostSummary)?, &fields) } /// The allocation that prices queries, or `None` while the run's history is pending. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/context.rs b/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/context.rs index 01c70906eb..fbc922604d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/context.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/context.rs @@ -8,7 +8,9 @@ use pyo3::{IntoPyObjectExt, pyclass, pymethods}; use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, }; -use crate::bindings::_helpers::cost_telemetry::values::{COST_MODELS, process_object}; +use crate::bindings::_helpers::cost_telemetry::values::process_object; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; const CONTEXT_VARIABLE_NAME: &str = "sqlbuild_cost_resource_context"; const DEFAULT_PHASE: &str = "execute"; @@ -110,7 +112,7 @@ impl CostContext { } }; let context: Bound<'_, PyAny> = call_with( - &imported(py, COST_MODELS, "CostResourceContext")?, + &host::entry(py, ExecutorHost::CostResourceContext)?, &[ ("run_id", arguments.value("run_id")?), ("resource_type", arguments.value("resource_type")?), @@ -157,7 +159,7 @@ impl CostContext { DEFAULT_ATTEMPT.into_bound_py_any(py)? }; let context: Bound<'_, PyAny> = call_with( - &imported(py, COST_MODELS, "CostResourceContext")?, + &host::entry(py, ExecutorHost::CostResourceContext)?, &[ ("run_id", current.getattr("run_id")?), ("resource_type", arguments.value("resource_type")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/ledger.rs b/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/ledger.rs index d013a6d08a..04eb73e589 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/ledger.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/ledger.rs @@ -19,10 +19,10 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, within, }; use crate::bindings::_helpers::cost_telemetry::health::mark_ledger_failure; -use crate::bindings::_helpers::cost_telemetry::values::{COST_EXCEPTIONS, process_object}; +use crate::bindings::_helpers::cost_telemetry::values::process_object; use crate::bindings::_helpers::cursor::values::DATETIME_MODULE; - -const NATIVE_RECORDS: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; static LEDGER_LOCK: PyOnceLock> = PyOnceLock::new(); @@ -195,7 +195,7 @@ fn ledger_entry<'py>(line: &Bound<'py, PyAny>) -> PyResult> { ("completed_at", parsed("completed_at")?), ]; call_with( - &imported(py, NATIVE_RECORDS, "StatementLedgerEntry")?, + ®istry::class(py, RecordId::StatementLedgerEntry)?, &entry_fields, ) } @@ -235,7 +235,7 @@ pub(crate) fn read_ledger<'py>( error.get_type(py).name()? ); let artifact_error: Bound<'py, PyAny> = - imported(py, COST_EXCEPTIONS, "CostArtifactError")?.call1((message,))?; + registry::native(py, NativeAttr::CostArtifactError)?.call1((message,))?; mark_failure(run_id, &artifact_error)?; } Err(error) => return Err(error), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/snowflake.rs b/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/snowflake.rs index ebb850a906..36713b0271 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/snowflake.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/snowflake.rs @@ -22,8 +22,9 @@ use crate::bindings::_helpers::cost_telemetry::allocation::{ use crate::bindings::_helpers::cost_telemetry::health::consume_ledger_failure; use crate::bindings::_helpers::cost_telemetry::ledger::read_ledger; use crate::bindings::_helpers::cursor::values::DATETIME_MODULE; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS: &str = "sqlbuild._native"; const DEADLINE_SECONDS: f64 = 10.0; const QUERY_TIMEOUT_SECONDS: &str = "5"; const RUNNING_STATUS: &str = "RUNNING"; @@ -258,7 +259,7 @@ fn row_observation<'py>( } let tag: QueryTag = tag.unwrap_or_default(); let observation: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS, "QueryCostObservation")?, + ®istry::class(py, RecordId::QueryCostObservation)?, &[ ("query_id", query_id.str()?.into_any()), ("warehouse_name", warehouse_name.str()?.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/values.rs b/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/values.rs index f45635d43d..b615bace40 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/values.rs @@ -11,9 +11,6 @@ use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::cursor::values::DATETIME_MODULE; use crate::bindings::_helpers::state_rows::timestamps::offset_aware; -pub(crate) const COST_MODELS: &str = "sqlbuild.cost.models"; -pub(crate) const COST_TYPES: &str = "sqlbuild.cost.types"; -pub(crate) const COST_EXCEPTIONS: &str = "sqlbuild._native"; const DECIMAL_MODULE: &str = "decimal"; /// The methods an adapter with warehouse cost telemetry implements. const COST_AWARE_METHODS: [&str; 2] = ["cost_capability", "collect_run_cost"]; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cursor/conversion.rs b/crates/sqlbuild-python/src/bindings/_helpers/cursor/conversion.rs index 6d4b909315..1a8778bef5 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cursor/conversion.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cursor/conversion.rs @@ -18,10 +18,10 @@ use crate::bindings::_helpers::cursor::values::{ DATETIME_MODULE, civil_date, local_date_time, naive_date_time, plain_date, replaced, timedelta_micros, }; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -pub(crate) const MODELS_MODULE: &str = "sqlbuild._native"; const EXCEPTIONS_MODULE: &str = "sqlbuild.cursor_algebra.exceptions"; -const PLANNER_TYPES_MODULE: &str = "sqlbuild._native"; /// Cursor grain names, finest first. pub(crate) const GRAIN_NAMES: [(&str, Grain); 6] = [ ("second", Grain::Second), @@ -56,12 +56,11 @@ pub(crate) struct CursorSession<'py> { impl<'py> CursorSession<'py> { pub(crate) fn new(py: Python<'py>) -> PyResult { - let models = py.import(MODELS_MODULE)?; Ok(Self { py, - timestamp_class: models.getattr("TimestampValue")?, - date_class: models.getattr("DateValue")?, - integer_class: models.getattr("IntegerValue")?, + timestamp_class: registry::class(py, RecordId::TimestampValue)?, + date_class: registry::class(py, RecordId::DateValue)?, + integer_class: registry::class(py, RecordId::IntegerValue)?, inputs: RefCell::new(Vec::new()), failure: RefCell::new(None), }) @@ -135,7 +134,7 @@ impl<'py> CursorSession<'py> { return Ok(Some(*known)); } } - imported(self.py, PLANNER_TYPES_MODULE, "CursorGrain")?.call1((grain,))?; + registry::class(self.py, RecordId::CursorGrain)?.call1((grain,))?; Err(PyTypeError::new_err("cursor grain must be a CursorGrain")) } @@ -198,13 +197,13 @@ impl<'py> CursorSession<'py> { .iter() .find(|(_, known)| *known == grain) .map_or("", |(name, _)| name); - imported(self.py, PLANNER_TYPES_MODULE, "CursorGrain")?.call1((name,)) + registry::class(self.py, RecordId::CursorGrain)?.call1((name,)) } /// A new `AlignedInterval`, validated as its constructor validates. pub(crate) fn python_interval(&self, interval: &Interval) -> PyResult> { call_with( - &imported(self.py, MODELS_MODULE, "AlignedInterval")?, + ®istry::class(self.py, RecordId::AlignedInterval)?, &[ ("start", self.python_scalar(&interval.start)?), ("end", self.python_scalar(&interval.end)?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cursor/intervals.rs b/crates/sqlbuild-python/src/bindings/_helpers/cursor/intervals.rs index f522059e2b..2a940a21e7 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cursor/intervals.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cursor/intervals.rs @@ -17,12 +17,12 @@ use sqlbuild_cursor::algebra::main::split_into_batches::split_into_batches; use sqlbuild_cursor::algebra::main::validate_interval::validate_interval; use sqlbuild_cursor::algebra::models::{Interval, Scalar}; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::cursor::conversion::CursorSession; use crate::bindings::_helpers::cursor::scalars::as_scalar; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; -const MICROBATCH_MODELS_MODULE: &str = "sqlbuild.microbatches.models"; -const MICROBATCH_EXCEPTIONS_MODULE: &str = "sqlbuild.microbatches.exceptions"; const RAW_INTERVAL_CURSOR_TYPES: [&str; 2] = ["timestamp", "integer"]; /// One stored interval with its parsed bounds. @@ -112,11 +112,9 @@ fn merge_raw_intervals<'py>( let py: Python<'py> = intervals.py(); let kind: String = cursor_type.str()?.extract()?; if !RAW_INTERVAL_CURSOR_TYPES.contains(&kind.as_str()) { - return Err( - imported(py, MICROBATCH_EXCEPTIONS_MODULE, "MicrobatchStateError") - .and_then(|class| class.call1((format!("unsupported cursor type: {kind}"),))) - .map_or_else(|error| error, PyErr::from_value), - ); + return Err(host::entry(py, ExecutorHost::MicrobatchStateError) + .and_then(|class| class.call1((format!("unsupported cursor type: {kind}"),))) + .map_or_else(|error| error, PyErr::from_value)); } let session: CursorSession<'py> = CursorSession::new(py)?; let mut parsed: Vec> = Vec::new(); @@ -140,7 +138,7 @@ fn merge_raw_intervals<'py>( if let Some(error) = failure { return Err(error); } - let class: Bound<'py, PyAny> = imported(py, MICROBATCH_MODELS_MODULE, "MicrobatchInterval")?; + let class: Bound<'py, PyAny> = host::entry(py, ExecutorHost::MicrobatchInterval)?; let mut merged: Vec> = Vec::new(); for next in parsed { let Some(last) = merged.last_mut() else { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cursor/overrides.rs b/crates/sqlbuild-python/src/bindings/_helpers/cursor/overrides.rs index 268983ed83..a36a0be5fd 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cursor/overrides.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cursor/overrides.rs @@ -6,15 +6,17 @@ use pyo3::types::{PyInt, PyString, PyStringMethods}; use sqlbuild_cursor::algebra::main::parse_integer_text::parse_integer_text; use crate::bindings::_helpers::compile_driver::python_calls::imported; -use crate::bindings::_helpers::cursor::conversion::{MODELS_MODULE, algebra_error}; +use crate::bindings::_helpers::cursor::conversion::algebra_error; use crate::bindings::_helpers::cursor::values::DATETIME_MODULE; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; const DECIMAL_MODULE: &str = "decimal"; const INTEGER_AS_TIMESTAMP: &str = "an integer cursor cannot be used as a timestamp"; const TIMESTAMP_AS_INTEGER: &str = "a timestamp cursor cannot be used as an integer"; -fn instance_of(value: &Bound<'_, PyAny>, class: &str) -> PyResult { - value.is_instance(&imported(value.py(), MODELS_MODULE, class)?) +fn instance_of(value: &Bound<'_, PyAny>, class: RecordId) -> PyResult { + value.is_instance(®istry::class(value.py(), class)?) } /// A timestamp override as a `datetime`; dates start at midnight and text is ISO 8601. @@ -23,15 +25,15 @@ pub(crate) fn cursor_timestamp<'py>(value: &Bound<'py, PyAny>) -> PyResult = imported(py, DATETIME_MODULE, "datetime")?; - if instance_of(value, "DateValue")? { + if instance_of(value, RecordId::DateValue)? { let midnight: Bound<'py, PyAny> = datetime.getattr("min")?.call_method0("time")?; return datetime.call_method1("combine", (value.getattr("value")?, midnight)); } - if instance_of(value, "IntegerValue")? { + if instance_of(value, RecordId::IntegerValue)? { return Err(algebra_error(py, INTEGER_AS_TIMESTAMP)); } datetime.call_method1("fromisoformat", (value,)) @@ -43,10 +45,10 @@ pub(crate) fn cursor_integer<'py>(value: &Bound<'py, PyAny>) -> PyResult() diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cursor/scalars.rs b/crates/sqlbuild-python/src/bindings/_helpers/cursor/scalars.rs index f551d426da..3648587691 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cursor/scalars.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cursor/scalars.rs @@ -25,11 +25,14 @@ use sqlbuild_cursor::algebra::main::render::render; use sqlbuild_cursor::algebra::models::{Extreme, Grain, Scalar, ScalarValue, SortKey, ZoneOffset}; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; -use crate::bindings::_helpers::cursor::conversion::{CursorSession, MODELS_MODULE, algebra_error}; +use crate::bindings::_helpers::cursor::conversion::{CursorSession, algebra_error}; use crate::bindings::_helpers::cursor::values::{DATETIME_MODULE, naive_date_time}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; const DECIMAL_MODULE: &str = "decimal"; -const TYPES_MODULE: &str = "sqlbuild.cursor_algebra.types"; const INTEGER_CURSOR: &str = "integer"; const UTC_OFFSET: i64 = 0; const TIMESTAMP_CURSOR: &str = "timestamp"; @@ -45,7 +48,7 @@ fn chained(py: Python<'_>, message: String, cause: PyErr) -> PyErr { fn parse_integer<'py>(raw: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = raw.py(); let text: String = raw.str()?.to_str()?.to_owned(); - let class: Bound<'py, PyAny> = imported(py, MODELS_MODULE, "IntegerValue")?; + let class: Bound<'py, PyAny> = registry::class(py, RecordId::IntegerValue)?; if let Some(value) = parse_integer_text(&text) { return call_with(&class, &[("value", value.into_pyobject(py)?.into_any())]); } @@ -105,7 +108,7 @@ fn parse_timestamp_text<'py>(raw: &Bound<'py, PyAny>, text: &str) -> PyResult { return call_with( - &imported(py, MODELS_MODULE, "DateValue")?, + ®istry::class(py, RecordId::DateValue)?, &[("value", date)], ); } @@ -117,7 +120,7 @@ fn parse_timestamp_text<'py>(raw: &Bound<'py, PyAny>, text: &str) -> PyResult call_with( - &imported(py, MODELS_MODULE, "TimestampValue")?, + ®istry::class(py, RecordId::TimestampValue)?, &[ ("value", value), ("source_text", PyString::new(py, &normalized).into_any()), @@ -150,13 +153,13 @@ pub(crate) fn parse_value<'py>( if raw.is_instance(&imported(py, DATETIME_MODULE, "datetime")?)? { let value: Bound<'py, PyAny> = utc_normalized(raw)?; return call_with( - &imported(py, MODELS_MODULE, "TimestampValue")?, + ®istry::class(py, RecordId::TimestampValue)?, &[("value", value)], ); } if raw.is_instance(&imported(py, DATETIME_MODULE, "date")?)? { return call_with( - &imported(py, MODELS_MODULE, "DateValue")?, + ®istry::class(py, RecordId::DateValue)?, &[("value", raw.clone())], ); } @@ -224,7 +227,7 @@ pub(crate) fn render_value(value: &Bound<'_, PyAny>) -> PyResult { #[pyfunction(name = "cursor_sentinel_to_token")] #[pyo3(signature = (*, sentinel))] pub(crate) fn sentinel_to_token(sentinel: &Bound<'_, PyAny>) -> PyResult { - if sentinel.is_instance(&imported(sentinel.py(), TYPES_MODULE, "BoundSentinel")?)? { + if sentinel.is_instance(&host::entry(sentinel.py(), ExecutorHost::BoundSentinel)?)? { return sentinel.getattr("value")?.extract(); } render_value(sentinel) @@ -235,7 +238,7 @@ pub(crate) fn sentinel_to_token(sentinel: &Bound<'_, PyAny>) -> PyResult #[pyo3(signature = (*, token))] fn sentinel_from_token<'py>(token: &Bound<'py, PyAny>) -> PyResult>> { let py: Python<'py> = token.py(); - match imported(py, TYPES_MODULE, "BoundSentinel")?.call1((token,)) { + match host::entry(py, ExecutorHost::BoundSentinel)?.call1((token,)) { Ok(sentinel) => Ok(Some(sentinel)), Err(error) if error.is_instance_of::(py) => Ok(None), Err(error) => Err(error), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dag_artifact/artifact.rs b/crates/sqlbuild-python/src/bindings/_helpers/dag_artifact/artifact.rs index bc1c66c99a..5a25b40ee2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dag_artifact/artifact.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dag_artifact/artifact.rs @@ -11,14 +11,12 @@ use sqlbuild_core::json::errors::JsonEmitError; use crate::bindings::_helpers::boundary::panics::compiler_guard; use crate::bindings::_helpers::dag_artifact::rows::{project_input, python_graph_input}; use crate::bindings::_helpers::graph::project_graph::NativeProjectGraph; - -const SPEC_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; fn python_error(error: DagArtifactError, py: pyo3::Python<'_>) -> PyErr { match error { - DagArtifactError::LoaderDestination(message) => py - .import(SPEC_EXCEPTIONS_MODULE) - .and_then(|module| module.getattr("SpecConfigError")) + DagArtifactError::LoaderDestination(message) => registry::error(py, ErrorId::SpecConfig) .and_then(|class| class.call1((message,))) .map_or_else(|error| error, PyErr::from_value), DagArtifactError::MissingParameterType(name) => PyKeyError::new_err(name), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dag_artifact/projection.rs b/crates/sqlbuild-python/src/bindings/_helpers/dag_artifact/projection.rs index d697b98371..9e4c85ea6b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dag_artifact/projection.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dag_artifact/projection.rs @@ -14,11 +14,10 @@ use sqlbuild_analysis::dag_artifact::models::{ DagProjectionCheck, DagProjectionGraph, DagProjectionNode, }; -use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::dag_artifact::selection::selected_node_ids; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::third_party::ThirdPartyHost; -const DAGSTER_CONSTANTS: &str = "sqlbuild.integrations.dagster.constants"; -const RIVERS_CONSTANTS: &str = "sqlbuild.integrations.rivers.constants"; const PROJECT_NAME_KEY: &str = "project_name"; const DEFAULT_MATERIALIZATION: &str = "table"; @@ -112,15 +111,15 @@ fn translated_nodes<'py>(artifact: &ArtifactGraph<'py>) -> PyResult, (module, name): (&str, &str)) -> PyResult> { - listed(&imported(py, module, name)?)? +fn kind_set(py: Python<'_>, constant: ThirdPartyHost) -> PyResult> { + listed(&host::entry(py, constant)?)? .iter() .map(text) .collect() } -fn constant_text(py: Python<'_>, (module, name): (&str, &str)) -> PyResult { - text(&imported(py, module, name)?) +fn constant_text(py: Python<'_>, constant: ThirdPartyHost) -> PyResult { + text(&host::entry(py, constant)?) } fn picked<'py>(objects: &[Bound<'py, PyAny>], indexes: &[usize]) -> Vec> { @@ -148,13 +147,13 @@ fn dagster_asset_plans<'py>(dag: &Bound<'py, PyAny>) -> PyResult = artifact_graph(dag, false)?; let upstream_nodes: Vec> = translated_nodes(&artifact)?; let policy: AssetProjectionPolicy = AssetProjectionPolicy { - asset_kinds: kind_set(py, (DAGSTER_CONSTANTS, "DAGSTER_ASSET_NODE_KINDS"))?, + asset_kinds: kind_set(py, ThirdPartyHost::DagsterAssetNodeKinds)?, upstream_asset_kinds_only: false, }; let kinds: AssetSpecKindPolicy = AssetSpecKindPolicy { - model_kind: constant_text(py, (DAGSTER_CONSTANTS, "MODEL_NODE_KIND"))?, - view_materialization: constant_text(py, (DAGSTER_CONSTANTS, "VIEW_MATERIALIZATION_TYPE"))?, - direct_kinds: kind_set(py, (DAGSTER_CONSTANTS, "DAGSTER_DIRECT_KIND_NODE_KINDS"))?, + model_kind: constant_text(py, ThirdPartyHost::ModelNodeKind)?, + view_materialization: constant_text(py, ThirdPartyHost::ViewMaterializationType)?, + direct_kinds: kind_set(py, ThirdPartyHost::DagsterDirectKindNodeKinds)?, }; let plans: Vec> = dag_asset_plans(&artifact.graph, &policy) .iter() @@ -187,7 +186,7 @@ fn rivers_asset_plans<'py>(dag: &Bound<'py, PyAny>) -> PyResult = dag.py(); let artifact: ArtifactGraph<'py> = artifact_graph(dag, false)?; let policy: AssetProjectionPolicy = AssetProjectionPolicy { - asset_kinds: kind_set(py, (RIVERS_CONSTANTS, "RIVERS_ASSET_NODE_KINDS"))?, + asset_kinds: kind_set(py, ThirdPartyHost::RiversAssetNodeKinds)?, upstream_asset_kinds_only: true, }; let plans: Vec> = dag_asset_plans(&artifact.graph, &policy) @@ -226,10 +225,8 @@ fn dagster_check_plans<'py>( let py: Python<'py> = dag.py(); let artifact: ArtifactGraph<'py> = artifact_graph(dag, true)?; let checked_nodes: Vec> = translated_nodes(&artifact)?; - let scenario: BTreeSet = BTreeSet::from([constant_text( - py, - (DAGSTER_CONSTANTS, "SCENARIO_CHECK_KIND"), - )?]); + let scenario: BTreeSet = + BTreeSet::from([constant_text(py, ThirdPartyHost::ScenarioCheckKind)?]); let filter: CheckKindFilter = CheckKindFilter { excluded: if include_scenarios || only_scenarios { BTreeSet::new() @@ -273,8 +270,7 @@ fn dagster_selected_asset_nodes<'py>( nodes: nodes.iter().map(selection_node).collect::>()?, ..DagProjectionGraph::default() }; - let asset_kinds: BTreeSet = - kind_set(py, (DAGSTER_CONSTANTS, "DAGSTER_ASSET_NODE_KINDS"))?; + let asset_kinds: BTreeSet = kind_set(py, ThirdPartyHost::DagsterAssetNodeKinds)?; let translated: Vec> = dag_selected_asset_nodes(&graph, &selected, &asset_kinds) .iter() diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dag_artifact/selection.rs b/crates/sqlbuild-python/src/bindings/_helpers/dag_artifact/selection.rs index 8fcf88a7ab..041d164ad9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dag_artifact/selection.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dag_artifact/selection.rs @@ -16,9 +16,10 @@ use sqlbuild_analysis::dag_artifact::models::{ use sqlbuild_analysis::planner_selection::main::unit_test_selector_rejection::unit_test_selector_rejection; use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::third_party::ThirdPartyHost; use crate::bindings::_helpers::planner_selection::carrier::Carrier; -const DAGSTER_EXCEPTIONS_MODULE: &str = "sqlbuild.integrations.dagster.exceptions"; const PATHLIB_MODULE: &str = "pathlib"; const JSON_MODULE: &str = "json"; const ABC_MODULE: &str = "collections.abc"; @@ -99,11 +100,9 @@ fn selection_error(py: Python<'_>, error: DagSelectionError, raw: &str) -> PyErr DagSelectionError::UnitTest(selector) => { carrier.error(unit_test_selector_rejection(&selector)) } - DagSelectionError::Input(message) => { - imported(py, DAGSTER_EXCEPTIONS_MODULE, "DagsterDagInputError") - .and_then(|class| class.call1((message,))) - .map_or_else(|failure| failure, PyErr::from_value) - } + DagSelectionError::Input(message) => host::entry(py, ThirdPartyHost::DagsterDagInputError) + .and_then(|class| class.call1((message,))) + .map_or_else(|failure| failure, PyErr::from_value), } } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/access.rs b/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/access.rs index a9adb97882..b30bb28e28 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/access.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/access.rs @@ -5,9 +5,8 @@ use std::collections::HashSet; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyList, PyTuple}; -use crate::bindings::_helpers::compile_driver::python_calls::imported; - -pub(crate) const CONSTANTS_MODULE: &str = "sqlbuild.integrations.dagster.constants"; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::third_party::ThirdPartyHost; /// An asset path as Dagster spells it. pub(crate) type AssetPath = Vec; @@ -61,17 +60,20 @@ pub(crate) fn asset_path_or_empty(node: &Bound<'_, PyAny>) -> PyResult(py: Python<'py>, name: &str) -> PyResult> { - imported(py, CONSTANTS_MODULE, name) +pub(crate) fn constant<'py>(py: Python<'py>, name: ThirdPartyHost) -> PyResult> { + host::entry(py, name) } /// Whether a constant set holds `value`. -pub(crate) fn constant_contains(py: Python<'_>, (name, value): (&str, &str)) -> PyResult { +pub(crate) fn constant_contains( + py: Python<'_>, + (name, value): (ThirdPartyHost, &str), +) -> PyResult { constant(py, name)?.contains(value) } /// `bool(record.get(, True))`. -pub(crate) fn enabled(record: &Bound<'_, PyAny>, field_constant: &str) -> PyResult { +pub(crate) fn enabled(record: &Bound<'_, PyAny>, field_constant: ThirdPartyHost) -> PyResult { let py: Python<'_> = record.py(); let field: Bound<'_, PyAny> = constant(py, field_constant)?; record.call_method1("get", (field, true))?.is_truthy() @@ -79,12 +81,12 @@ pub(crate) fn enabled(record: &Bound<'_, PyAny>, field_constant: &str) -> PyResu /// Whether a translated DAG node is owned by the SQLBuild asset definition. pub(crate) fn asset_enabled(node: &Bound<'_, PyAny>) -> PyResult { - enabled(node, "DAGSTER_ASSET_ENABLED_FIELD") + enabled(node, ThirdPartyHost::DagsterAssetEnabledField) } /// Whether a translated DAG check is owned by the SQLBuild asset definition. pub(crate) fn check_enabled(check: &Bound<'_, PyAny>) -> PyResult { - enabled(check, "DAGSTER_CHECK_ENABLED_FIELD") + enabled(check, ThirdPartyHost::DagsterCheckEnabledField) } /// `{str(node.get("id")): node for node in dag.get("nodes", ())}`, keeping the last node per id. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/arguments.rs b/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/arguments.rs index a01d52fa43..ea438177fd 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/arguments.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/arguments.rs @@ -19,6 +19,7 @@ use crate::bindings::_helpers::dagster_invocation::access::{ constant_contains, get, items, node_with_id, nodes_by_id, path_set, string_tuple, text_of, }; use crate::bindings::_helpers::dagster_invocation::results::{dagster_check_name, sorted_nodes}; +use crate::bindings::_helpers::native_host::third_party::ThirdPartyHost; const SELECT_FILE_FLAG: &str = "--select-file"; const EVENT_OUTPUT_FLAG: &str = "--event-output"; @@ -176,7 +177,7 @@ fn scenario_selection<'py>( selected_ids.insert(text_of(&node, "id")?); } } - let scenario_kind: String = constant(dag.py(), "SCENARIO_CHECK_KIND")?.extract()?; + let scenario_kind: String = constant(dag.py(), ThirdPartyHost::ScenarioCheckKind)?.extract()?; let mut selectors: Vec = Vec::new(); for check in items(dag, "checks")? { if !check_enabled(&check)? || text_of(&check, "kind")? != scenario_kind { @@ -220,10 +221,10 @@ fn asset_selection<'py>( if is_scenario_test(args) { return scenario_selection((args, dag), read_selection); } - if !constant_contains(py, ("ASSET_SELECTION_COMMANDS", &args[0]))? { + if !constant_contains(py, (ThirdPartyHost::AssetSelectionCommands, &args[0]))? { return Ok(unselected(args)); } - let explicit: Bound<'py, PyAny> = constant(py, "EXPLICIT_SELECTION_FLAGS")?; + let explicit: Bound<'py, PyAny> = constant(py, ThirdPartyHost::ExplicitSelectionFlags)?; for argument in args { if explicit.contains(argument)? { return Ok(unselected(args)); @@ -246,9 +247,9 @@ fn asset_selection<'py>( let kinds: Bound<'py, PyAny> = constant( py, if args[0] == LOAD_COMMAND { - "LOAD_SELECTABLE_NODE_KINDS" + ThirdPartyHost::LoadSelectableNodeKinds } else { - "DEFAULT_SELECTABLE_NODE_KINDS" + ThirdPartyHost::DefaultSelectableNodeKinds }, )?; let mut selectors: Vec = Vec::new(); @@ -313,7 +314,7 @@ pub(crate) fn dagster_cli_arguments<'py>( let mut json_path: Option> = None; if managed && !json_output_requested(&resolved) - && (constant_contains(py, ("ASSET_SELECTION_COMMANDS", &resolved[0]))? + && (constant_contains(py, (ThirdPartyHost::AssetSelectionCommands, &resolved[0]))? || is_scenario_test(&resolved)) { let path: Bound<'py, PyAny> = temporary_file(py, EXECUTION_JSON_FILE, &[])?; @@ -328,7 +329,7 @@ pub(crate) fn dagster_cli_arguments<'py>( json_path = Some(imported(py, "pathlib", "Path")?.call1((path,))?); } let event_path: Option> = if managed - && constant_contains(py, ("LIVE_EVENT_COMMANDS", &resolved[0]))? + && constant_contains(py, (ThirdPartyHost::LiveEventCommands, &resolved[0]))? && !resolved .iter() .any(|argument| argument == EVENT_OUTPUT_FLAG) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/failures.rs b/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/failures.rs index 3cf7b1e638..66b048599c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/failures.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/failures.rs @@ -11,6 +11,7 @@ use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; use crate::bindings::_helpers::dagster_invocation::access::{ constant_contains, get, items, text_of, }; +use crate::bindings::_helpers::native_host::third_party::ThirdPartyHost; const LIVE_FAILURE_MESSAGE: &str = "SQLBuild resource attempt failed; see final execution diagnostics"; @@ -94,7 +95,7 @@ pub(crate) fn dagster_failure<'py>( let limits: Bound<'py, PyDict> = PyDict::new(py); for asset in items(&payload, "assets")? { let status: String = text_of(&asset, "status")?; - if constant_contains(py, ("COMPLETED_EXECUTION_STATUSES", &status))? { + if constant_contains(py, (ThirdPartyHost::CompletedExecutionStatuses, &status))? { continue; } let failure: AssetFailure = asset_failure(&asset)?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/results.rs b/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/results.rs index 7dbad46a46..add0bd57a2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/results.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/results.rs @@ -15,6 +15,7 @@ use crate::bindings::_helpers::dagster_invocation::access::{ constant_contains, get, items, merged, node_with_id, nodes_by_id, path_set, string_list, text_of, value_or, }; +use crate::bindings::_helpers::native_host::third_party::ThirdPartyHost; const CHECK_KIND_FALLBACK: &str = "check"; const CHECK_NAME_JOIN: &str = "__"; @@ -47,7 +48,7 @@ pub(crate) fn sorted_nodes<'py>(dag: &Bound<'py, PyAny>) -> PyResult) -> PyResult { let py: Python<'_> = check.py(); - let field: Bound<'_, PyAny> = constant(py, "DAGSTER_CHECK_NAME_FIELD")?; + let field: Bound<'_, PyAny> = constant(py, ThirdPartyHost::DagsterCheckNameField)?; let translated: Bound<'_, PyAny> = check.call_method1("get", (field,))?; if !translated.is_none() { return translated.str()?.extract(); @@ -110,7 +111,7 @@ fn metadata_value<'py>(value: &Bound<'py, PyAny>) -> PyResult> /// A record's non-null metadata, without check internals. fn metadata_from_mapping<'py>(value: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = value.py(); - let excluded: Bound<'py, PyAny> = constant(py, "CHECK_METADATA_EXCLUDED_KEYS")?; + let excluded: Bound<'py, PyAny> = constant(py, ThirdPartyHost::CheckMetadataExcludedKeys)?; let metadata: Bound<'py, PyDict> = PyDict::new(py); for pair in value.call_method0("items")?.try_iter()? { let (key, item): (Bound<'py, PyAny>, Bound<'py, PyAny>) = pair?.extract()?; @@ -164,7 +165,7 @@ fn loader_result<'py>( ) -> PyResult)>> { let py: Python<'py> = dag.py(); let nodes = nodes_by_id(dag)?; - let loader_kind: String = constant(py, "LOADER_NODE_KIND")?.extract()?; + let loader_kind: String = constant(py, ThirdPartyHost::LoaderNodeKind)?.extract()?; let source_id: String = text_of(source_node, "id")?; for edge in items(dag, "edges")? { if text_of(&edge, "to_id")? != source_id { @@ -218,8 +219,8 @@ struct CheckFilter<'a> { fn warn_severity(check: &Bound<'_, PyAny>) -> PyResult { let py: Python<'_> = check.py(); - let warning: String = constant(py, "WARNING_CHECK_SEVERITY")?.extract()?; - let insufficient: String = constant(py, "INSUFFICIENT_CHECK_STATUS")?.extract()?; + let warning: String = constant(py, ThirdPartyHost::WarningCheckSeverity)?.extract()?; + let insufficient: String = constant(py, ThirdPartyHost::InsufficientCheckStatus)?.extract()?; let status: String = text_of(check, "status")?; Ok(status == insufficient || status == warning || text_of(check, "severity")? == warning) } @@ -345,7 +346,7 @@ fn keep_checks_only<'py>( } fn completed(py: Python<'_>, status: &str) -> PyResult { - constant_contains(py, ("COMPLETED_EXECUTION_STATUSES", status)) + constant_contains(py, (ThirdPartyHost::CompletedExecutionStatuses, status)) } /// Dagster events for an execution JSON document. @@ -375,9 +376,9 @@ pub(crate) fn dagster_payload_results<'py>( let payload: Bound<'py, PyAny> = arguments.value("payload")?; let command: Vec = arguments.value("command")?.extract()?; let selection = SelectionFacts::from_arguments(&arguments)?; - let clone_command: String = constant(py, "CLONE_COMMAND")?.extract()?; - let success: String = constant(py, "SUCCESS_EXECUTION_STATUS")?.extract()?; - let source_kind: String = constant(py, "SOURCE_NODE_KIND")?.extract()?; + let clone_command: String = constant(py, ThirdPartyHost::CloneCommand)?.extract()?; + let success: String = constant(py, ThirdPartyHost::SuccessExecutionStatus)?.extract()?; + let source_kind: String = constant(py, ThirdPartyHost::SourceNodeKind)?.extract()?; let is_clone: bool = text_of(&payload, "command")? == clone_command; let selected_paths: HashSet = if is_clone { HashSet::new() @@ -502,8 +503,8 @@ pub(crate) fn dagster_integration_results<'py>( let emitted_asset_paths: Bound<'py, PyAny> = arguments.value("emitted_asset_paths")?; let selection = SelectionFacts::from_arguments(&arguments)?; let nodes = nodes_by_id(&dag)?; - let source_kind: String = constant(py, "SOURCE_NODE_KIND")?.extract()?; - let clone_command: String = constant(py, "CLONE_COMMAND")?.extract()?; + let source_kind: String = constant(py, ThirdPartyHost::SourceNodeKind)?.extract()?; + let clone_command: String = constant(py, ThirdPartyHost::CloneCommand)?.extract()?; let envelope_command: String = envelope.getattr("command")?.str()?.extract()?; let mut plans: Vec> = Vec::new(); let asset: Bound<'py, PyAny> = envelope.getattr("asset")?; @@ -576,7 +577,10 @@ pub(crate) fn dagster_integration_results<'py>( )?); } } - let standalone: bool = constant_contains(py, ("STANDALONE_CHECK_COMMANDS", &envelope_command))?; + let standalone: bool = constant_contains( + py, + (ThirdPartyHost::StandaloneCheckCommands, &envelope_command), + )?; let emitted: HashSet = path_set(&emitted_asset_paths)?; let filter = CheckFilter { selection: &selection, @@ -617,7 +621,7 @@ pub(crate) fn dagster_selected_asset_results<'py>( ) -> PyResult>> { let py: Python<'py> = dag.py(); let selected: HashSet = path_set(&selected_asset_paths)?; - let materializable: Bound<'py, PyAny> = constant(py, "MATERIALIZABLE_NODE_KINDS")?; + let materializable: Bound<'py, PyAny> = constant(py, ThirdPartyHost::MaterializableNodeKinds)?; let mut plans: Vec> = Vec::new(); for node in sorted_nodes(&dag)? { if !selected.is_empty() && !selected.contains(&asset_path(&node)?) { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/translator.rs b/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/translator.rs index 671f34c8e0..c911d37cec 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/translator.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/translator.rs @@ -11,7 +11,9 @@ use sqlbuild_core::dagster_invocation::main::fallback_asset_description::fallbac use sqlbuild_core::text::main::python_strip::python_strip; use crate::bindings::_helpers::compile_driver::python_calls::imported; -use crate::bindings::_helpers::dagster_invocation::access::{CONSTANTS_MODULE, get, value_or}; +use crate::bindings::_helpers::dagster_invocation::access::{get, value_or}; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::third_party::ThirdPartyHost; const ABC_MODULE: &str = "collections.abc"; const BUILTINS_MODULE: &str = "builtins"; @@ -55,8 +57,7 @@ fn abc_instance(value: &Bound<'_, PyAny>, class: &str) -> PyResult { /// A source node's non-empty, non-text `meta.dagster.asset_key` sequence. fn authored_asset_key<'py>(node: &Bound<'py, PyAny>) -> PyResult>> { - let source_kind: String = - imported(node.py(), CONSTANTS_MODULE, "SOURCE_NODE_KIND")?.extract()?; + let source_kind: String = host::entry(node.py(), ThirdPartyHost::SourceNodeKind)?.extract()?; if text(&get(node, "kind")?)? != source_kind { return Ok(None); } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/auto_init.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/auto_init.rs index 6d67f1d171..eabf813bc3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/auto_init.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/auto_init.rs @@ -11,14 +11,12 @@ use sqlbuild_core::dbt_command::main::forwarded_dbt_args::forwarded_dbt_args; use sqlbuild_core::dbt_command::main::render_dbt_auto_init::render_dbt_auto_init; use sqlbuild_core::dbt_command::models::ForwardedArg; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::dbt_command::support::{color_enabled, flag, path, stream}; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use sqlbuild_core::constants::PROJECT_CONFIG_FILENAME; -const PROGRESS_REPORTER_MODULE: &str = "sqlbuild._native"; fn project_config_found(directory: &Bound<'_, PyAny>) -> PyResult { let name: Bound<'_, PyAny> = @@ -82,14 +80,14 @@ pub(crate) fn ensure_twin_project<'py>( !no_color && !args.iter().any(|arg| arg == JSON_OPTION) && color_enabled(py)?; let progress_stream: Bound<'py, PyAny> = stream(py, "stderr")?; let progress: Bound<'py, PyAny> = call_with( - &imported(py, PROGRESS_REPORTER_MODULE, "DbtInitProgressReporter")?, + ®istry::native(py, NativeAttr::DbtInitProgressReporter)?, &[ ("stream", progress_stream.clone()), ("use_color", flag(py, use_color)), ], )?; let callbacks: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DbtInitProgressCallbacks")?, + ®istry::class(py, RecordId::DbtInitProgressCallbacks)?, &[ ("start", progress.getattr("start")?), ("complete", progress.getattr("complete")?), @@ -105,7 +103,7 @@ pub(crate) fn ensure_twin_project<'py>( None => none.clone(), }; let request: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DbtInitRequest")?, + ®istry::class(py, RecordId::DbtInitRequest)?, &[ ("cwd", effective_dir), ("dbt_project_dir", dbt_project_dir), @@ -120,7 +118,7 @@ pub(crate) fn ensure_twin_project<'py>( ], )?; let result: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "build_dbt_init_project")?, + ®istry::native(py, NativeAttr::BuildDbtInitProject)?, &[("request", request)], )?; let project_file: String = result.getattr("project_file")?.str()?.to_string(); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/command.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/command.rs index 7f1200a679..0e86d93fec 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/command.rs @@ -6,18 +6,14 @@ use pyo3::{pyfunction, wrap_pyfunction}; use sqlbuild_core::dbt_command::main::dbt_output_options::dbt_output_options; use sqlbuild_core::dbt_command::models::DbtOutputOptions; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::dbt_command::auto_init::{args_tuple, ensure_twin_project}; use crate::bindings::_helpers::dbt_command::debug::run_dbt_debug; use crate::bindings::_helpers::dbt_command::support::{color_enabled, effective_dir, flag, stream}; use crate::bindings::_helpers::dbt_interop::runtime_config::validate_dbt_execution_args; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const PLAN_MODULE: &str = "sqlbuild._native"; -const EXECUTE_MODULE: &str = "sqlbuild._native"; -const PRESENTATION_MODELS: &str = "sqlbuild._native"; const PLAN_COMMAND: &str = "plan"; const DEBUG_COMMAND: &str = "debug"; const BOUNDED_SECTION_ENTRIES: i64 = 10; @@ -41,7 +37,7 @@ impl<'py> Progress<'py> { }, )?; let reporter: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "PlanningProgressReporter")?, + ®istry::native(py, NativeAttr::PlanningProgressReporter)?, &[ ("stream", stream.clone()), ("use_color", flag(py, use_color)), @@ -70,7 +66,7 @@ fn run_plan(project_dir: &Bound<'_, PyAny>, args: &[String], no_color: bool) -> progress.blank_line()?; } let plan: Bound<'_, PyAny> = call_with( - &imported(py, PLAN_MODULE, "plan_dbt_interop_from_project")?, + ®istry::native(py, NativeAttr::PlanDbtInteropFromProject)?, &[ ("project_dir", project_dir.clone()), ("args", args_tuple(py, &options.routed_args)?), @@ -80,7 +76,7 @@ fn run_plan(project_dir: &Bound<'_, PyAny>, args: &[String], no_color: bool) -> ], )?; let display: Bound<'_, PyAny> = call_with( - &imported(py, PRESENTATION_MODELS, "DisplayOptions")?, + ®istry::class(py, RecordId::DisplayOptions)?, &[( "max_entries_per_section", if options.verbose { @@ -91,7 +87,7 @@ fn run_plan(project_dir: &Bound<'_, PyAny>, args: &[String], no_color: bool) -> )], )?; let rendered: String = call_with( - &imported(py, NATIVE_MODULE, "render_dbt_interop_plan")?, + ®istry::native(py, NativeAttr::RenderDbtInteropPlan)?, &[ ("plan", plan), ("json_output", flag(py, options.json_output)), @@ -116,7 +112,7 @@ fn run_execution( let progress: Progress<'_> = Progress::new(py, &options, no_color)?; progress.blank_line()?; let request: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "DbtInteropExecutionRequest")?, + ®istry::class(py, RecordId::DbtInteropExecutionRequest)?, &[ ("command", command.clone()), ("project_dir", project_dir.clone()), @@ -129,7 +125,7 @@ fn run_execution( ("json_output", flag(py, options.json_output)), ], )?; - imported(py, EXECUTE_MODULE, "execute_dbt_interop_from_project")? + registry::native(py, NativeAttr::ExecuteDbtInteropFromProject)? .call1((request,))? .extract() } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/debug.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/debug.rs index 5146169ccc..3c63ed14d7 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/debug.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/debug.rs @@ -3,12 +3,11 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use sqlbuild_core::dbt_command::constants::NO_CONNECTION_OPTION; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::dbt_command::auto_init::args_tuple; use crate::bindings::_helpers::dbt_command::support::{color_enabled, flag, status, stream}; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const DEBUG_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; /// Run `body` under a stderr status line that is always closed; returns its exit code. fn with_status( @@ -35,7 +34,7 @@ pub(crate) fn run_dbt_debug( let use_color: bool = !no_color && color_enabled(py)?; let dbt_code: i64 = with_status(py, ("Running dbt debug...", use_color), || { call_with( - &imported(py, DEBUG_MODULE, "debug_dbt_from_project")?, + ®istry::native(py, NativeAttr::DebugDbtFromProject)?, &[ ("project_dir", project_dir.clone()), ("args", args_tuple(py, args)?), @@ -49,7 +48,7 @@ pub(crate) fn run_dbt_debug( let sqlbuild_code: i64 = with_status(py, ("Running SQLBuild diagnostics...", use_color), || { call_with( - &imported(py, NATIVE_MODULE, "run_debug_command")?, + ®istry::native(py, NativeAttr::RunDebugCommand)?, &[ ("project_dir", project_dir.clone()), ("no_color", flag(py, no_color)), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/init.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/init.rs index 5ead8833fe..ed3ffd5488 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/init.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/init.rs @@ -13,18 +13,14 @@ use sqlbuild_core::dbt_command::models::DbtInitSummary; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; use crate::bindings::_helpers::dbt_command::support::{color_enabled, flag, path, stream}; - -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ERRORS_MODULE: &str = "sqlbuild._native"; -const PROGRESS_REPORTER_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; fn missing_project(py: Python<'_>) -> PyErr { let raised = (|| { let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("code", "C242")?; - imported(py, ERRORS_MODULE, "CliUserError")?.call( + registry::error(py, ErrorId::CliUser)?.call( ("sqb dbt init requires --project-dir pointing at a dbt project",), Some(&options), ) @@ -84,21 +80,21 @@ fn run_dbt_init_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult< let project_dir: Bound<'_, PyAny> = dbt_project_dir(request)?; let use_color: bool = color_enabled(py)?; let progress: Bound<'_, PyAny> = call_with( - &imported(py, PROGRESS_REPORTER_MODULE, "DbtInitProgressReporter")?, + ®istry::native(py, NativeAttr::DbtInitProgressReporter)?, &[ ("stream", stream(py, "stdout")?), ("use_color", flag(py, use_color)), ], )?; let callbacks: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DbtInitProgressCallbacks")?, + ®istry::class(py, RecordId::DbtInitProgressCallbacks)?, &[ ("start", progress.getattr("start")?), ("complete", progress.getattr("complete")?), ], )?; let init_request: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DbtInitRequest")?, + ®istry::class(py, RecordId::DbtInitRequest)?, &[ ("cwd", request.getattr("cwd")?), ("dbt_project_dir", project_dir), @@ -119,11 +115,11 @@ fn run_dbt_init_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult< ], )?; call_with( - &imported(py, NATIVE_MODULE, "validate_dbt_init_project")?, + ®istry::native(py, NativeAttr::ValidateDbtInitProject)?, &[("request", init_request.clone())], )?; let result: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "build_dbt_init_project")?, + ®istry::native(py, NativeAttr::BuildDbtInitProject)?, &[("request", init_request)], )?; let style: CliStyle = CliStyle { use_color }; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/init_project.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/init_project.rs index da84c3fb43..435a07c977 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/init_project.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/init_project.rs @@ -20,11 +20,10 @@ use crate::bindings::_helpers::dbt_interop::runner::launch_os_error; use crate::bindings::types::CompilerDetach; use sqlbuild_config::dbt_profile::models::DbtProjectMetadata; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const DBT_EXCEPTIONS: &str = "sqlbuild.integrations.dbt.exceptions"; -const PROFILE_RENDER_MODULE: &str = "sqlbuild.integrations.dbt._helpers.profile.render"; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::third_party::ThirdPartyHost; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use sqlbuild_core::constants::PROJECT_CONFIG_FILENAME as PROJECT_FILE; const DEFAULT_OUTPUT_DIR: &str = "sqlbuild_project"; const PROFILES_FILE: &str = "profiles.yml"; @@ -82,11 +81,7 @@ fn build_dbt_init_project<'py>(request: &Bound<'py, PyAny>) -> PyResult = call_with( - &imported( - py, - PROFILE_RENDER_MODULE, - "render_selected_dbt_profile_output", - )?, + &host::entry(py, ThirdPartyHost::RenderSelectedDbtProfileOutput)?, &[ ("selected", selected.clone()), ("project_dir", paths.dbt_project_dir.clone()), @@ -94,7 +89,7 @@ fn build_dbt_init_project<'py>(request: &Bound<'py, PyAny>) -> PyResult = call_with( - &imported(py, NATIVE_MODULE, "normalize_dbt_profile_connection")?, + ®istry::native(py, NativeAttr::NormalizeDbtProfileConnection)?, &[("resolved", resolved_output)], )?; let target_path: Bound<'py, PyAny> = resolved( @@ -104,7 +99,7 @@ fn build_dbt_init_project<'py>(request: &Bound<'py, PyAny>) -> PyResult = selected.getattr("target_name")?; let toml: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "build_dbt_init_project_toml")?, + ®istry::native(py, NativeAttr::BuildDbtInitProjectToml)?, &[ ("project_name", project_name.clone().into_bound_py_any(py)?), ("adapter", normalized.getattr("adapter")?), @@ -148,7 +143,7 @@ fn build_dbt_init_project<'py>(request: &Bound<'py, PyAny>) -> PyResult, error: &PyErr) -> bool { } fn profile_error(py: Python<'_>, message: String) -> PyErr { - match imported(py, DBT_EXCEPTIONS, "DbtProfileError").and_then(|class| class.call1((message,))) + match host::entry(py, ThirdPartyHost::DbtProfileError).and_then(|class| class.call1((message,))) { Ok(instance) => PyErr::from_value(instance), Err(error) => error, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_events/results.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_events/results.rs index 241b31a3cb..537bf6b09d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_events/results.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_events/results.rs @@ -5,11 +5,9 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyString, PyTuple}; use sqlbuild_core::dbt_events::models::{DbtNodeMessage, DbtNodeResult}; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; - -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_RECORDS: &str = "sqlbuild._native"; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; fn optional<'py, T: pyo3::IntoPyObject<'py>>( py: Python<'py>, @@ -34,7 +32,7 @@ pub(crate) fn python_result<'py>( py: Python<'py>, result: &DbtNodeResult, ) -> PyResult> { - let message_class: Bound<'py, PyAny> = imported(py, NATIVE_RECORDS, "DbtNodeMessage")?; + let message_class: Bound<'py, PyAny> = registry::class(py, RecordId::DbtNodeMessage)?; let messages: Vec> = result .messages .iter() @@ -49,7 +47,7 @@ pub(crate) fn python_result<'py>( }) .collect::>()?; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DbtNodeExecutionResult")?, + ®istry::class(py, RecordId::DbtNodeExecutionResult)?, &[ ("unique_id", text(py, &result.unique_id)), ("resource_type", text(py, &result.resource_type)), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/cli.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/cli.rs index e8af0fab78..7584104b63 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/cli.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/cli.rs @@ -1,6 +1,5 @@ //! dbt argv building, executable choice, `dbt ls` output, `sqb dbt` argument routing and config. -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class_at; use pyo3::prelude::{ Bound, PyAny, PyAnyMethods, PyErr, PyModule, PyModuleMethods, PyResult, Python, }; @@ -22,10 +21,9 @@ use sqlbuild_core::text::main::python_strip::python_strip; use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, }; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS: &str = "sqlbuild._native"; -const DBT_TYPES: &str = "sqlbuild._native"; -const DBT_EXCEPTIONS: &str = "sqlbuild.integrations.dbt.exceptions"; const DBT_EXECUTABLE_ENV_VAR: &str = "DBT_EXECUTABLE"; const COMMAND_ARGV_OPTIONS: (&[&str], &[&str]) = (&["dbt_executable", "command", "options"], &["args"]); @@ -225,7 +223,7 @@ fn parse_dbt_ls_json_lines<'py>(py: Python<'py>, stdout: &str) -> PyResult(py: Python<'py>, stdout: &str) -> PyResult> { - let node_class: Bound<'py, PyAny> = imported(py, NATIVE_RECORDS, "DbtLsNode")?; + let node_class: Bound<'py, PyAny> = registry::class(py, RecordId::DbtLsNode)?; let mut nodes: Vec> = Vec::new(); for line in python_splitlines(stdout) { let stripped: &str = python_strip(line); @@ -242,7 +240,7 @@ pub(crate) fn ls_nodes<'py>(py: Python<'py>, stdout: &str) -> PyResult, class_name: &str, error: DbtCliError) -> PyErr { +pub(crate) fn raised(py: Python<'_>, class_name: ErrorId, error: DbtCliError) -> PyErr { let created = || -> PyResult { let keywords: Bound<'_, PyDict> = PyDict::new(py); if let Some(code) = error.code { @@ -251,8 +249,8 @@ pub(crate) fn raised(py: Python<'_>, class_name: &str, error: DbtCliError) -> Py if let Some(help) = error.help { keywords.set_item("help", help)?; } - let instance: Bound<'_, PyAny> = shape_class_at(py, DBT_EXCEPTIONS, class_name)? - .call((error.message,), Some(&keywords))?; + let instance: Bound<'_, PyAny> = + registry::error(py, class_name)?.call((error.message,), Some(&keywords))?; Ok(PyErr::from_value(instance)) }; created().unwrap_or_else(|failure| failure) @@ -299,11 +297,11 @@ fn route_dbt_interop_args<'py>( let command: String = command.str()?.extract()?; let flags: DbtParsedFlags = parsed_flags(parsed)?; let (kind, routed): (DbtInteropCommandKind, DbtRoutedArgs) = - route(&command, &flags).map_err(|error| raised(py, "DbtInteropArgumentError", error))?; + route(&command, &flags).map_err(|error| raised(py, ErrorId::DbtInteropArgument, error))?; let command: Bound<'py, PyAny> = - imported(py, DBT_TYPES, "DbtInteropCommand")?.call1((kind.name(),))?; + registry::class(py, RecordId::DbtInteropCommand)?.call1((kind.name(),))?; call_with( - &imported(py, NATIVE_RECORDS, "DbtInteropRoutedArgs")?, + ®istry::class(py, RecordId::DbtInteropRoutedArgs)?, &[ ("command", command), ("select", PyTuple::new(py, flags.select)?.into_any()), @@ -378,13 +376,13 @@ fn resolve_dbt_config<'py>( )?; let choice: DbtConfigChoice = choose_dbt_config(sources, arguments.flag("require_project_dir")?) - .map_err(|error| raised(py, "DbtInteropConfigError", error))?; + .map_err(|error| raised(py, ErrorId::DbtInteropConfig, error))?; let target: Bound<'py, PyAny> = choice.target.map_or_else( || py.None().into_bound(py), |target| PyString::new(py, &target).into_any(), ); call_with( - &imported(py, NATIVE_RECORDS, "ResolvedDbtConfig")?, + ®istry::class(py, RecordId::ResolvedDbtConfig)?, &[ ( "project_dir", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/event_stream.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/event_stream.rs index 911be9bf38..fad85edd01 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/event_stream.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/event_stream.rs @@ -27,9 +27,10 @@ use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, use crate::bindings::_helpers::dbt_interop::runner::{ dbt_command_lifecycle, launch_failure, launch_os_error, report_exit, }; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use crate::bindings::types::CompilerDetach; -const NATIVE_MODULE: &str = "sqlbuild._native"; const STREAM_OPTIONS: (&[&str], &[&str]) = ( &["argv", "cwd", "stream", "use_color", "target_path"], &[ @@ -96,7 +97,7 @@ fn execute_dbt_json_event_stream<'py>( arguments.value("detail_by_unique_id")?, )?; keywords.set_item("use_color", output.use_color)?; - imported(py, NATIVE_MODULE, "DbtEventStream")?.call((), Some(&keywords))? + registry::native(py, NativeAttr::DbtEventStream)?.call((), Some(&keywords))? }; let argv: Vec = arguments.value("argv")?.extract()?; let cwd: Option = arguments.value("cwd")?.extract()?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/graph.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/graph.rs index c29ad70a61..61ee6fb002 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/graph.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/graph.rs @@ -19,16 +19,11 @@ use sqlbuild_core::dbt_graph::models::{ }; use sqlbuild_core::dbt_graph::types::DbtSelectorResolver; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::dbt_interop::manifest::resolve_sqlbuild_model_dbt_refs; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_RECORDS: &str = "sqlbuild._native"; -const SELECTOR_EXPANSION_MODULE: &str = "sqlbuild._native"; -const MODEL_SELECTORS_MODULE: &str = "sqlbuild._native"; const MODEL_RESOURCE_TYPE: &str = "model"; const SELECTION_OPTIONS: (&[&str], &[&str]) = ( &["project", "graph", "select"], @@ -189,12 +184,8 @@ pub(crate) struct PlannerResolver<'py> { pub(crate) fn planner_resolver(project: Bound<'_, PyAny>) -> PyResult> { let py: Python<'_> = project.py(); Ok(PlannerResolver { - split: imported(py, SELECTOR_EXPANSION_MODULE, "split_selector_expansion")?, - resolve: imported( - py, - MODEL_SELECTORS_MODULE, - "resolve_sqlbuild_model_selector_names", - )?, + split: registry::native(py, NativeAttr::SplitSelectorExpansion)?, + resolve: registry::native(py, NativeAttr::ResolveSqlbuildModelSelectorNames)?, project, }) } @@ -232,9 +223,8 @@ fn selection_error(py: Python<'_>, error: DbtSelectionError) -> PyErr { let raised = || -> PyResult { let keywords: Bound<'_, PyDict> = PyDict::new(py); keywords.set_item("code", PATH_SELECTOR_ERROR_CODE)?; - let error: Bound<'_, PyAny> = - imported(py, NATIVE_RECORDS_MODULE, "DbtInteropArgumentError")? - .call((message,), Some(&keywords))?; + let error: Bound<'_, PyAny> = registry::error(py, ErrorId::DbtInteropArgument)? + .call((message,), Some(&keywords))?; Ok(PyErr::from_value(error)) }; raised().unwrap_or_else(|failure| failure) @@ -277,7 +267,7 @@ fn selection_result<'py>(py: Python<'py>, selection: DbtSelection) -> PyResult>()?; call_with( - &imported(py, NATIVE_RECORDS, "DbtInteropSelectionResult")?, + ®istry::class(py, RecordId::DbtInteropSelectionResult)?, &[ ( "sqlbuild_model_names", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/manifest.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/manifest.rs index da8afaf6f7..666f734777 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/manifest.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/manifest.rs @@ -22,9 +22,9 @@ use sqlbuild_config::dbt_manifest::models::{ use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, }; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS: &str = "sqlbuild._native"; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; const DBT_REF_KIND: &str = "dbt_ref"; const RELATION_OPTIONS: (&[&str], &[&str]) = (&["manifest", "ref_kind", "ref_name", "ref_package"], &[]); @@ -34,8 +34,8 @@ fn raised(py: Python<'_>, error: ManifestError) -> PyErr { let keywords: Bound<'_, PyDict> = PyDict::new(py); keywords.set_item("code", error.code)?; keywords.set_item("help", error.help)?; - let instance: Bound<'_, PyAny> = imported(py, COMPILE_ERRORS_MODULE, "CompileInputError")? - .call((error.message,), Some(&keywords))?; + let instance: Bound<'_, PyAny> = + registry::error(py, ErrorId::CompileInput)?.call((error.message,), Some(&keywords))?; Ok(PyErr::from_value(instance)) }; created().unwrap_or_else(|failure| failure) @@ -139,7 +139,7 @@ fn model_object<'py>( ) -> PyResult> { let py: Python<'py> = raw.py(); call_with( - &imported(py, NATIVE_RECORDS, "DbtManifestModel")?, + ®istry::class(py, RecordId::DbtManifestModel)?, &[ ("unique_id", unique_id.clone()), ("package_name", text(py, &model.package_name)), @@ -170,7 +170,7 @@ fn seed_object<'py>( ) -> PyResult> { let py: Python<'py> = raw.py(); call_with( - &imported(py, NATIVE_RECORDS, "DbtManifestSeed")?, + ®istry::class(py, RecordId::DbtManifestSeed)?, &[ ("unique_id", unique_id.clone()), ("package_name", text(py, &seed.package_name)), @@ -191,7 +191,7 @@ fn source_object<'py>( ) -> PyResult> { let py: Python<'py> = raw.py(); call_with( - &imported(py, NATIVE_RECORDS, "DbtManifestSource")?, + ®istry::class(py, RecordId::DbtManifestSource)?, &[ ("unique_id", unique_id.clone()), ("package_name", text(py, &source.package_name)), @@ -337,7 +337,7 @@ fn manifest_index<'py>(raw_data: &Bound<'py, PyAny>) -> PyResult( || py.None().into_bound(py), |detail| PyString::new(py, &detail).into_any(), ); - let error: Bound<'py, PyAny> = imported(py, "sqlbuild._native", "DbtInteropRuntimeError")? + let error: Bound<'py, PyAny> = registry::error(py, ErrorId::DbtInteropRuntime)? .call(("dbt ls failed",), Some(&keywords(py, &[("help", help)])?))?; Err(PyErr::from_value(error)) } @@ -86,7 +87,7 @@ fn command_kind(command: &Bound<'_, PyAny>) -> PyResult { "debug" => Ok(DbtInteropCommandKind::Debug), _ => Err(raised( command.py(), - "DbtInteropArgumentError", + ErrorId::DbtInteropArgument, DbtCliError { message: format!("Unsupported sqb dbt command: {value}"), ..DbtCliError::default() @@ -141,7 +142,7 @@ fn plan_dbt_interop_command<'py>( let arguments: KeywordArguments<'py> = KeywordArguments::read(py, "plan_dbt_interop_command", options, PLAN_OPTIONS)?; let command: Bound<'py, PyAny> = - imported(py, DBT_TYPES, "DbtInteropCommand")?.call1((arguments.value("command")?,))?; + registry::class(py, RecordId::DbtInteropCommand)?.call1((arguments.value("command")?,))?; let command_args: Bound<'py, PyAny> = arguments.value("command_args")?; let (select, exclude) = ( command_args.getattr("select")?, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/pipeline.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/pipeline.rs index d22fbee4a5..0045d597c7 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/pipeline.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/pipeline.rs @@ -9,15 +9,12 @@ use sqlbuild_core::dbt_cli::main::dbt_debug_args::dbt_debug_args; use std::time::Instant; use crate::bindings::_helpers::build_command::support::{boolean, none, text}; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::dbt_interop::pipeline_prologue::progress; use crate::bindings::_helpers::dbt_interop::runtime_config::config_overrides; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_RECORDS: &str = "sqlbuild._native"; const PLAN_OPTIONS: (&[&str], &[&str]) = ( &["project_dir", "args"], &[ @@ -37,10 +34,10 @@ const DEBUG_OPTIONS: (&[&str], &[&str]) = ( fn native<'py>( py: Python<'py>, - name: &str, + name: NativeAttr, pairs: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - call_with(&imported(py, "sqlbuild._native", name)?, pairs) + call_with(®istry::native(py, name)?, pairs) } fn prepare<'py>( @@ -48,10 +45,10 @@ fn prepare<'py>( ) -> PyResult<(Bound<'py, PyAny>, Bound<'py, PyAny>, Bound<'py, PyAny>)> { let py = request.py(); let invocation = - imported(py, "sqlbuild._native", "resolve_dbt_execution_invocation")?.call1((request,))?; + registry::native(py, NativeAttr::ResolveDbtExecutionInvocation)?.call1((request,))?; let manifest = native( py, - "load_compiled_dbt_manifest", + NativeAttr::LoadCompiledDbtManifest, &[ ("runner", invocation.getattr("runner")?), ("dbt_options", invocation.getattr("dbt_options")?), @@ -61,7 +58,7 @@ fn prepare<'py>( )?; let compiled = native( py, - "compile_dbt_interop_project", + NativeAttr::CompileDbtInteropProject, &[ ("project_dir", request.getattr("project_dir")?), ( @@ -85,7 +82,7 @@ fn resolve_plan<'py>( ) -> PyResult> { native( request.py(), - "resolve_dbt_interop_plan", + NativeAttr::ResolveDbtInteropPlan, &[ ("command", request.getattr("command")?), ("invocation", invocation.clone()), @@ -105,10 +102,10 @@ fn resolve_plan<'py>( fn execute_dbt_interop_from_project(request: &Bound<'_, PyAny>) -> PyResult { let py = request.py(); let command = request.getattr("command")?; - let kind = imported(py, "sqlbuild._native", "DbtInteropCommand")?; + let kind = registry::class(py, RecordId::DbtInteropCommand)?; if !command.eq(kind.getattr("RUN")?)? && !command.eq(kind.getattr("BUILD")?)? { return Err(PyErr::from_value( - imported(py, "sqlbuild._native", "DbtInteropArgumentError")?.call1((format!( + registry::error(py, ErrorId::DbtInteropArgument)?.call1((format!( "unsupported dbt interop execution command: {}", command.str()? ),))?, @@ -119,7 +116,7 @@ fn execute_dbt_interop_from_project(request: &Bound<'_, PyAny>) -> PyResult let plan = resolution.getattr("plan")?; let merged = native( py, - "build_merged_dbt_execution_argv", + NativeAttr::BuildMergedDbtExecutionArgv, &[ ("command", command), ("options", invocation.getattr("dbt_options")?), @@ -132,7 +129,7 @@ fn execute_dbt_interop_from_project(request: &Bound<'_, PyAny>) -> PyResult )?; native( py, - "write_dbt_execution_plan_text", + NativeAttr::WriteDbtExecutionPlanText, &[ ("request", request.clone()), ("invocation", invocation.clone()), @@ -142,7 +139,7 @@ fn execute_dbt_interop_from_project(request: &Bound<'_, PyAny>) -> PyResult )?; let execution = native( py, - "execute_dbt_without_state_tracking", + NativeAttr::ExecuteDbtWithoutStateTracking, &[ ("request", request.clone()), ("invocation", invocation.clone()), @@ -151,7 +148,7 @@ fn execute_dbt_interop_from_project(request: &Bound<'_, PyAny>) -> PyResult )?; native( py, - "write_dbt_execution_summary", + NativeAttr::WriteDbtExecutionSummary, &[ ("request", request.clone()), ("invocation", invocation.clone()), @@ -160,7 +157,7 @@ fn execute_dbt_interop_from_project(request: &Bound<'_, PyAny>) -> PyResult )?; let failed = native( py, - "build_failed_sqlbuild_model_names", + NativeAttr::BuildFailedSqlbuildModelNames, &[ ("graph", resolution.getattr("graph")?), ("manifest", manifest.clone()), @@ -173,7 +170,7 @@ fn execute_dbt_interop_from_project(request: &Bound<'_, PyAny>) -> PyResult } let output = native( py, - "resolve_sqlbuild_execution_plan_output", + NativeAttr::ResolveSqlbuildExecutionPlanOutput, &[ ("request", request.clone()), ("invocation", invocation.clone()), @@ -188,7 +185,13 @@ fn execute_dbt_interop_from_project(request: &Bound<'_, PyAny>) -> PyResult return Ok(code); } Some("No SQLBuild work selected.") - } else if !native(py, "plan_has_executable_work", &[("plan", output.clone())])?.is_truthy()? { + } else if !native( + py, + NativeAttr::PlanHasExecutableWork, + &[("plan", output.clone())], + )? + .is_truthy()? + { Some("Skipping SQLBuild: no executable work selected.") } else { None @@ -196,7 +199,7 @@ fn execute_dbt_interop_from_project(request: &Bound<'_, PyAny>) -> PyResult if let Some(message) = skip { native( py, - "write_sqlbuild_skip_notice", + NativeAttr::WriteSqlbuildSkipNotice, &[ ("request", request.clone()), ("invocation", invocation), @@ -207,7 +210,7 @@ fn execute_dbt_interop_from_project(request: &Bound<'_, PyAny>) -> PyResult } let connection = native( py, - "resolve_dbt_connection_config", + NativeAttr::ResolveDbtConnectionConfig, &[ ("request", request.clone()), ("invocation", invocation.clone()), @@ -216,7 +219,7 @@ fn execute_dbt_interop_from_project(request: &Bound<'_, PyAny>) -> PyResult )?; let result = native( py, - "run_dbt_sqlbuild_work", + NativeAttr::RunDbtSqlbuildWork, &[ ("request", request.clone()), ("invocation", invocation), @@ -237,11 +240,11 @@ fn plan_dbt_interop_from_project<'py>( ) -> PyResult> { let args = KeywordArguments::read(py, "plan_dbt_interop_from_project", options, PLAN_OPTIONS)?; let request = call_with( - &imported(py, NATIVE_RECORDS, "DbtInteropExecutionRequest")?, + ®istry::class(py, RecordId::DbtInteropExecutionRequest)?, &[ ( "command", - imported(py, "sqlbuild._native", "DbtInteropCommand")?.getattr("PLAN")?, + registry::class(py, RecordId::DbtInteropCommand)?.getattr("PLAN")?, ), ("project_dir", args.value("project_dir")?), ("args", args.value("args")?), @@ -271,7 +274,7 @@ fn plan_dbt_interop_from_project<'py>( } else { native( py, - "ConnectionProgressReporter", + NativeAttr::ConnectionProgressReporter, &[ ("adapter_name", compiled.getattr("adapter_name")?), ("stream", stream), @@ -283,7 +286,7 @@ fn plan_dbt_interop_from_project<'py>( let resolution = resolve_plan(&request, &invocation, &compiled, &manifest)?; let plan = native( py, - "attach_sqlbuild_plan_output", + NativeAttr::AttachSqlbuildPlanOutput, &[ ("plan", resolution.getattr("plan")?), ("project_dir", args.value("project_dir")?), @@ -317,7 +320,7 @@ fn debug_dbt_from_project(py: Python<'_>, options: Option>) -> let args = KeywordArguments::read(py, "debug_dbt_from_project", options, DEBUG_OPTIONS)?; let discovered = native( py, - "discover_project_inputs", + NativeAttr::DiscoverProjectInputs, &[("project_dir", args.value("project_dir")?)], )?; let dbt_args: Vec = args @@ -328,7 +331,7 @@ fn debug_dbt_from_project(py: Python<'_>, options: Option>) -> let overrides = config_overrides(py, &dbt_args)?; let resolved = native( py, - "resolve_dbt_config", + NativeAttr::ResolveDbtConfig, &[ ("project_root", args.value("project_dir")?), ( @@ -344,7 +347,7 @@ fn debug_dbt_from_project(py: Python<'_>, options: Option>) -> ], )?; let dbt_options = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DbtCliOptions")?, + ®istry::class(py, RecordId::DbtCliOptions)?, &[ ("project_dir", resolved.getattr("project_dir")?), ("profiles_dir", resolved.getattr("profiles_dir")?), @@ -354,7 +357,7 @@ fn debug_dbt_from_project(py: Python<'_>, options: Option>) -> let mut runner = args.value("dbt_runner")?; if !runner.is_truthy()? { runner = call_with( - &imported(py, "sqlbuild._native", "DbtRunner")?, + ®istry::native(py, NativeAttr::DbtRunner)?, &[("dbt_executable", args.value("dbt_executable")?)], )?; } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/pipeline_phases.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/pipeline_phases.rs index 7a5d4ff0ac..f3e96c7711 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/pipeline_phases.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/pipeline_phases.rs @@ -10,8 +10,9 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ }; use crate::bindings::_helpers::dbt_interop::pipeline_prologue::progress; use crate::bindings::_helpers::dbt_interop::plan_output::{connection_config, connection_hooks}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS: &str = "sqlbuild._native"; const OPTIONS: (&[&str], &[&str]) = ( &["request", "invocation"], &[ @@ -31,7 +32,7 @@ fn environment<'py>(args: &KeywordArguments<'py>) -> PyResult> let request = args.value("request")?; let compiled = args.value("compiled")?; call_with( - &imported(request.py(), NATIVE_RECORDS, "DbtPlanEnvironment")?, + ®istry::class(request.py(), RecordId::DbtPlanEnvironment)?, &[ ("project_dir", request.getattr("project_dir")?), ( @@ -90,7 +91,7 @@ fn write_dbt_execution_plan_text( pyo3::types::PyInt::new(py, super::constants::PLAN_DISPLAY_LIMIT).into_any() }; let rendered = call_with( - &imported(py, "sqlbuild._native", "render_dbt_interop_plan")?, + ®istry::native(py, NativeAttr::RenderDbtInteropPlan)?, &[ ("plan", display), ("json_output", boolean(py, false)), @@ -98,7 +99,7 @@ fn write_dbt_execution_plan_text( ( "display_options", call_with( - &imported(py, "sqlbuild._native", "DisplayOptions")?, + ®istry::class(py, RecordId::DisplayOptions)?, &[("max_entries_per_section", limit)], )?, ), @@ -121,7 +122,7 @@ fn execute_dbt_without_state_tracking<'py>( let request = args.value("request")?; let invocation = args.value("invocation")?; call_with( - &imported(py, "sqlbuild._native", "execute_dbt_commands")?, + ®istry::native(py, NativeAttr::ExecuteDbtCommands)?, &[ ("runner", invocation.getattr("runner")?), ("options", invocation.getattr("dbt_options")?), @@ -144,11 +145,7 @@ fn execute_dbt_without_state_tracking<'py>( fn write_dbt_execution_summary(py: Python<'_>, options: Option>) -> PyResult<()> { let args = KeywordArguments::read(py, "write_dbt_execution_summary", options, OPTIONS)?; let footer = call_with( - &imported( - py, - "sqlbuild._native", - "render_dbt_execution_summary_footer", - )?, + ®istry::native(py, NativeAttr::RenderDbtExecutionSummaryFooter)?, &[ ( "node_results", @@ -191,7 +188,7 @@ fn resolve_sqlbuild_execution_plan_output<'py>( let stream = invocation.getattr("output_stream")?; emit(&stream, "\n")?; let reporter = call_with( - &imported(py, "sqlbuild._native", "ConnectionProgressReporter")?, + ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, &[ ( "adapter_name", @@ -203,7 +200,7 @@ fn resolve_sqlbuild_execution_plan_output<'py>( ], )?; let request = call_with( - &imported(py, NATIVE_RECORDS, "DbtSqlbuildPlanRequest")?, + ®istry::class(py, RecordId::DbtSqlbuildPlanRequest)?, &[ ("selected_model_names", selected), ( @@ -217,7 +214,7 @@ fn resolve_sqlbuild_execution_plan_output<'py>( ], )?; call_with( - &imported(py, "sqlbuild._native", "build_sqlbuild_plan_output")?, + ®istry::native(py, NativeAttr::BuildSqlbuildPlanOutput)?, &[ ("environment", environment(&args)?), ("request", request), @@ -232,10 +229,10 @@ fn execution_limits<'py>( ) -> PyResult> { let py = discovered.py(); if target.is_none() { - return imported(py, "sqlbuild._native", "ExecutionLimitsConfig")?.call0(); + return registry::class(py, RecordId::ExecutionLimitsConfig)?.call0(); } call_with( - &imported(py, "sqlbuild._native", "resolve_target_config")?, + ®istry::native(py, NativeAttr::ResolveTargetConfig)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), @@ -259,7 +256,7 @@ fn run_dbt_sqlbuild_work<'py>( let project = compiled.getattr("project")?; let discovered = invocation.getattr("discovered_inputs")?; let context = call_with( - &imported(py, "sqlbuild._native", "DbtSqlbuildWorkContext")?, + ®istry::class(py, RecordId::DbtSqlbuildWorkContext)?, &[ ("plan_output", args.value("plan_output")?), ("connection_config", args.value("connection_config")?), @@ -278,7 +275,7 @@ fn run_dbt_sqlbuild_work<'py>( ], )?; call_with( - &imported(py, "sqlbuild._native", "execute_dbt_sqlbuild_work")?, + ®istry::native(py, NativeAttr::ExecuteDbtSqlbuildWork)?, &[ ("context", context), ("command", request.getattr("command")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/pipeline_prologue.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/pipeline_prologue.rs index f77d959915..30c8a18552 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/pipeline_prologue.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/pipeline_prologue.rs @@ -1,8 +1,6 @@ //! Discover, compile and resolve the combined dbt pipeline through its existing boundaries. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use pyo3::prelude::{ Bound, PyAny, PyAnyMethods, PyErr, PyModule, PyModuleMethods, PyResult, Python, }; @@ -12,10 +10,11 @@ use crate::bindings::_helpers::dbt_interop::reference_resolver::DbtCompileRefere use crate::bindings::_helpers::dbt_interop::runtime_config::{ interop_adapter, manifest_path, parse_dbt_execution_args, plan_options, vars_mapping, }; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::{pyfunction, wrap_pyfunction}; use std::time::Instant; -const NATIVE_RECORDS: &str = "sqlbuild._native"; const COMPILE_OPTIONS: (&[&str], &[&str]) = ( &[ "project_dir", @@ -65,7 +64,7 @@ pub(crate) fn resolve_dbt_execution_invocation<'py>( let py = request.py(); let mut executable = request.getattr("dbt_executable")?; if !executable.is_truthy()? { - executable = imported(py, "sqlbuild._native", "resolve_dbt_executable")?.call0()?; + executable = registry::native(py, NativeAttr::ResolveDbtExecutable)?.call0()?; } let mut stream = request.getattr("progress_stream")?; if !stream.is_truthy()? { @@ -74,12 +73,12 @@ pub(crate) fn resolve_dbt_execution_invocation<'py>( let command = request.getattr("command")?; let parsed = parse_dbt_execution_args(&command, &request.getattr("args")?)?; let routed = call_with( - &imported(py, "sqlbuild._native", "route_dbt_interop_args")?, + ®istry::native(py, NativeAttr::RouteDbtInteropArgs)?, &[("command", command), ("parsed", parsed)], )?; let project_dir = request.getattr("project_dir")?; let discovered = call_with( - &imported(py, "sqlbuild._native", "discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[("project_dir", project_dir.clone())], )?; let dbt_args: Vec = routed @@ -97,13 +96,13 @@ pub(crate) fn resolve_dbt_execution_invocation<'py>( let mut runner = request.getattr("dbt_runner")?; if !runner.is_truthy()? { runner = call_with( - &imported(py, "sqlbuild._native", "DbtRunner")?, + ®istry::native(py, NativeAttr::DbtRunner)?, &[("dbt_executable", executable.clone())], )?; } let dbt_stream = request.getattr("dbt_stdout_stream")?; call_with( - &imported(py, NATIVE_RECORDS, "DbtInteropInvocation")?, + ®istry::class(py, RecordId::DbtInteropInvocation)?, &[ ("dbt_executable", executable), ("output_stream", stream.clone()), @@ -144,8 +143,8 @@ fn load_compiled_dbt_manifest<'py>( ], )?; if result.getattr("returncode")?.extract::()? != 0 { - let detail = imported(py, "sqlbuild._native", "dbt_failure_detail")?.call1((result,))?; - let error = imported(py, "sqlbuild._native", "DbtInteropRuntimeError")?.call( + let detail = registry::native(py, NativeAttr::DbtFailureDetail)?.call1((result,))?; + let error = registry::error(py, ErrorId::DbtInteropRuntime)?.call( ("dbt compile failed",), Some( &crate::bindings::_helpers::compile_driver::python_calls::keywords( @@ -161,7 +160,7 @@ fn load_compiled_dbt_manifest<'py>( progress(&callback, "Loading dbt manifest...")?; let path = manifest_path(&args.value("dbt_options")?)?; let manifest = call_with( - &imported(py, "sqlbuild._native", "load_dbt_manifest_index")?, + ®istry::native(py, NativeAttr::LoadDbtManifestIndex)?, &[("manifest_path", path)], )?; completed(&callback, "Loaded dbt manifest.", start)?; @@ -181,7 +180,7 @@ fn compile_dbt_interop_project<'py>( progress(&callback, "Compiling SQLBuild project...")?; let discovered = args.value("discovered_inputs")?; let name = call_with( - &imported(py, "sqlbuild._native", "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), @@ -194,7 +193,7 @@ fn compile_dbt_interop_project<'py>( )? .into_any(); let project = call_with( - &imported(py, "sqlbuild._native", "build_compiled_project")?, + ®istry::native(py, NativeAttr::BuildCompiledProject)?, &[ ("discovered_inputs", discovered), ("adapter", adapter.clone()), @@ -205,7 +204,7 @@ fn compile_dbt_interop_project<'py>( )?; completed(&callback, "Compiled SQLBuild project.", start)?; call_with( - &imported(py, NATIVE_RECORDS, "DbtInteropCompiledProject")?, + ®istry::class(py, RecordId::DbtInteropCompiledProject)?, &[ ("adapter_name", name), ("adapter", adapter), @@ -227,7 +226,7 @@ fn resolve_dbt_interop_plan<'py>( progress(&callback, "Building dbt interop graph...")?; let project = args.value("compiled")?.getattr("project")?; let graph = call_with( - &imported(py, "sqlbuild._native", "build_dbt_combined_graph")?, + ®istry::native(py, NativeAttr::BuildDbtCombinedGraph)?, &[ ("manifest", args.value("manifest")?), ("project", project.clone()), @@ -240,7 +239,7 @@ fn resolve_dbt_interop_plan<'py>( let routed = invocation.getattr("routed")?; let tuple = py.import("builtins")?.getattr("tuple")?; let command_args = call_with( - &imported(py, NATIVE_RECORDS, "DbtInteropCommandArgs")?, + ®istry::class(py, RecordId::DbtInteropCommandArgs)?, &[ ("select", tuple.call1((routed.getattr("select")?,))?), ("exclude", tuple.call1((routed.getattr("exclude")?,))?), @@ -257,7 +256,7 @@ fn resolve_dbt_interop_plan<'py>( ], )?; let plan = call_with( - &imported(py, "sqlbuild._native", "plan_dbt_interop_command")?, + ®istry::native(py, NativeAttr::PlanDbtInteropCommand)?, &[ ("command", args.value("command")?), ("project", project), @@ -270,7 +269,7 @@ fn resolve_dbt_interop_plan<'py>( )?; completed(&callback, "Resolved dbt and SQLBuild selection.", start)?; call_with( - &imported(py, NATIVE_RECORDS, "DbtInteropPlanResolution")?, + ®istry::class(py, RecordId::DbtInteropPlanResolution)?, &[("graph", graph), ("plan", plan)], ) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/plan.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/plan.rs index c47a3cf46f..90e993a695 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/plan.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/plan.rs @@ -8,13 +8,10 @@ use sqlbuild_core::cli_text::main::format_dbt_interop_plan::format_dbt_interop_p use sqlbuild_core::cli_text::main::format_dbt_interop_plan_json::format_dbt_interop_plan_json; use sqlbuild_core::cli_text::models::{DbtInteropPlanText, DbtLsNodeText, DisplayLimit}; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const DBT_TYPES: &str = "sqlbuild._native"; -const PRESENTATION_MODELS: &str = "sqlbuild._native"; const BUILD_OPTIONS: (&[&str], &[&str]) = ( &[ "command", @@ -123,7 +120,7 @@ fn skip_reason<'py>(py: Python<'py>, has_work: bool, member: &str) -> PyResult( let arguments: KeywordArguments<'py> = KeywordArguments::read(py, "build_dbt_interop_plan", options, BUILD_OPTIONS)?; let command: Bound<'py, PyAny> = - imported(py, DBT_TYPES, "DbtInteropCommand")?.call1((arguments.value("command")?,))?; + registry::class(py, RecordId::DbtInteropCommand)?.call1((arguments.value("command")?,))?; let nodes: Bound<'py, PyAny> = tuple_of(arguments.value("dbt_ls_nodes")?)?; let mut selected: Vec = Vec::new(); for node in nodes.try_iter()? { @@ -152,7 +149,7 @@ pub(crate) fn build_dbt_interop_plan<'py>( dbt_has_work || sqlbuild_has_work, ); call_with( - &imported(py, NATIVE_MODULE, "DbtInteropPlan")?, + ®istry::class(py, RecordId::DbtInteropPlan)?, &[ ("command", command), ( @@ -214,7 +211,7 @@ fn render_dbt_interop_plan(py: Python<'_>, options: Option>) - } let mut display: Bound<'_, PyAny> = arguments.value("display_options")?; if !display.is_truthy()? { - display = imported(py, PRESENTATION_MODELS, "DisplayOptions")?.call0()?; + display = registry::class(py, RecordId::DisplayOptions)?.call0()?; } let use_color: bool = arguments.flag("use_color")?; let plan_output: Bound<'_, PyAny> = plan.getattr("sqlbuild_plan_output")?; @@ -224,7 +221,7 @@ fn render_dbt_interop_plan(py: Python<'_>, options: Option>) - } else { Some( call_with( - &imported(py, NATIVE_MODULE, "format_plan")?, + ®istry::native(py, NativeAttr::FormatPlan)?, &[ ("plan", plan_output), ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/plan_output.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/plan_output.rs index 946cd50284..72ad4eeb3e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/plan_output.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/plan_output.rs @@ -12,12 +12,11 @@ use crate::bindings::_helpers::compile_driver::plan_result::connection_with_hook use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, keywords, }; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_RECORDS: &str = "sqlbuild._native"; -const PLANNER_MODELS: &str = "sqlbuild._native"; -const HOOKS: &str = "sqlbuild.runtime.contracts.models"; const BUILD_OPTIONS: (&[&str], &[&str]) = (&["environment", "request", "hooks"], &[]); const ATTACH_OPTIONS: (&[&str], &[&str]) = ( &[ @@ -38,7 +37,7 @@ pub(crate) fn connection_config<'py>( let py = environment.py(); let discovered = environment.getattr("discovered_inputs")?; let raw = call_with( - &imported(py, "sqlbuild._native", "build_effective_connection_config")?, + ®istry::native(py, NativeAttr::BuildEffectiveConnectionConfig)?, &[("discovered_inputs", discovered.clone())], )?; let raw: Bound<'_, PyDict> = raw.cast_into()?; @@ -56,7 +55,7 @@ pub(crate) fn connection_config<'py>( )?; let config = config.into_any(); call_with( - &imported(py, "sqlbuild._native", "apply_command_warehouse")?, + ®istry::native(py, NativeAttr::ApplyCommandWarehouse)?, &[ ("config", config), ("adapter_name", environment.getattr("adapter_name")?), @@ -100,7 +99,7 @@ pub(crate) fn connection_hooks<'py>( )); } } - call_with(&imported(py, HOOKS, "ConnectionHooks")?, &pairs) + call_with(&host::entry(py, ExecutorHost::ConnectionHooks)?, &pairs) } /// Open through the shared adapter boundary while retaining keyword-only timing callbacks. @@ -122,7 +121,7 @@ fn open_connection<'py>( }, )); } - let relayed = call_with(&imported(py, HOOKS, "ConnectionHooks")?, &pairs)?; + let relayed = call_with(&host::entry(py, ExecutorHost::ConnectionHooks)?, &pairs)?; connection_with_hooks(&environment.getattr("adapter")?, &config, &relayed) } @@ -146,7 +145,7 @@ pub(crate) fn build_sqlbuild_plan_output<'py>( let planned = (|| { let args = request.getattr("sqlbuild_args")?; let deferral = call_with( - &imported(py, "sqlbuild._native", "resolve_model_deferral_inputs")?, + ®istry::native(py, NativeAttr::ResolveModelDeferralInputs)?, &[ ("project", environment.getattr("project")?), ( @@ -159,7 +158,7 @@ pub(crate) fn build_sqlbuild_plan_output<'py>( ], )?; let cursor = call_with( - &imported(py, PLANNER_MODELS, "CursorOverrides")?, + ®istry::class(py, RecordId::CursorOverrides)?, &[ ("start_ts", flag_value(&args, "--start-cursor-ts")?), ("end_ts", flag_value(&args, "--end-cursor-ts")?), @@ -169,11 +168,11 @@ pub(crate) fn build_sqlbuild_plan_output<'py>( )?; let full_refresh = boolean(py, args.contains("--full-refresh")?); let selection = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "PlannerSelection")?, + ®istry::class(py, RecordId::PlannerSelection)?, &[("select", selected.clone())], )?; let overrides = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "PlannerOverrides")?, + ®istry::class(py, RecordId::PlannerOverrides)?, &[ ("cursor_overrides", cursor), ("full_refresh", full_refresh.clone()), @@ -185,7 +184,7 @@ pub(crate) fn build_sqlbuild_plan_output<'py>( )?; let discovered = environment.getattr("discovered_inputs")?; let result = call_with( - &imported(py, "sqlbuild._native", "build_execution_plan")?, + ®istry::native(py, NativeAttr::BuildExecutionPlan)?, &[ ("project", environment.getattr("project")?), ("adapter", environment.getattr("adapter")?), @@ -195,7 +194,7 @@ pub(crate) fn build_sqlbuild_plan_output<'py>( ("deferral", deferral), ( "policies", - imported(py, NATIVE_RECORDS_MODULE, "PlannerPolicies")?.call0()?, + registry::class(py, RecordId::PlannerPolicies)?.call0()?, ), ("on_progress", hooks.getattr("on_progress")?), ("project_config", discovered.getattr("project_config")?), @@ -203,12 +202,9 @@ pub(crate) fn build_sqlbuild_plan_output<'py>( ], ); match result { - Err(error) - if error - .is_instance(py, &imported(py, "sqlbuild._native", "PlannerInputError")?) => - { + Err(error) if error.is_instance(py, ®istry::error(py, ErrorId::PlannerInput)?) => { call_with( - &imported(py, "sqlbuild._native", "build_display_only_sqlbuild_plan")?, + ®istry::native(py, NativeAttr::BuildDisplayOnlySqlbuildPlan)?, &[ ("project", environment.getattr("project")?), ("selected_model_names", selected), @@ -236,7 +232,7 @@ fn attach_sqlbuild_plan_output<'py>( let compiled = args.value("compiled")?; let plan = args.value("plan")?; let environment = call_with( - &imported(py, NATIVE_RECORDS, "DbtPlanEnvironment")?, + ®istry::class(py, RecordId::DbtPlanEnvironment)?, &[ ("project_dir", args.value("project_dir")?), ("discovered_inputs", args.value("discovered_inputs")?), @@ -246,7 +242,7 @@ fn attach_sqlbuild_plan_output<'py>( ], )?; let request = call_with( - &imported(py, NATIVE_RECORDS, "DbtSqlbuildPlanRequest")?, + ®istry::class(py, RecordId::DbtSqlbuildPlanRequest)?, &[ ( "selected_model_names", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/profile.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/profile.rs index 2e1672a8f4..01dc966401 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/profile.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/profile.rs @@ -12,13 +12,12 @@ use sqlbuild_config::dbt_profile::models::{ ConnectionKey, ConnectionSource, DbtInitConfig, NormalizedProfile, ProfileEntry, ProfileValue, }; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; - -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::third_party::ThirdPartyHost; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const DBT_ERRORS_MODULE: &str = "sqlbuild.integrations.dbt.exceptions"; const INIT_TOML_OPTIONS: (&[&str], &[&str]) = ( &[ "project_name", @@ -35,8 +34,7 @@ const INIT_TOML_OPTIONS: (&[&str], &[&str]) = ( ); pub(crate) fn raised(py: Python<'_>, error: DbtProfileError) -> PyErr { - match imported(py, DBT_ERRORS_MODULE, "DbtProfileError") - .and_then(|class| class.call1((error.0,))) + match host::entry(py, ThirdPartyHost::DbtProfileError).and_then(|class| class.call1((error.0,))) { Ok(instance) => PyErr::from_value(instance), Err(failure) => failure, @@ -83,7 +81,7 @@ fn normalized_connection<'py>( connection.set_item(key, value)?; } call_with( - &imported(py, NATIVE_RECORDS_MODULE, "NormalizedDbtProfileConnection")?, + ®istry::class(py, RecordId::NormalizedDbtProfileConnection)?, &[ ("adapter", PyString::new(py, profile.adapter).into_any()), ("connection", connection.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/profile_connection.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/profile_connection.rs index 1b632d3c96..28be17377b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/profile_connection.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/profile_connection.rs @@ -7,17 +7,16 @@ use pyo3::types::{PyDict, PyDictMethods, PyString}; use pyo3::{pyfunction, wrap_pyfunction}; use sqlbuild_core::text::main::python_strip::python_strip; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::dbt_interop::cli::resolved_path; use crate::bindings::_helpers::dbt_interop::profile_load::{ default_profiles_dir, load_dbt_project_metadata, load_selected_dbt_profile_output, }; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::third_party::ThirdPartyHost; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const PROFILE_RENDER_MODULE: &str = "sqlbuild.integrations.dbt._helpers.profile.render"; -const DBT_ERRORS_MODULE: &str = "sqlbuild.integrations.dbt.exceptions"; const DBT_PROFILE_SOURCE: &str = "dbt_profile"; const PROFILE_CONNECTION_OPTIONS: (&[&str], &[&str]) = ( &[ @@ -31,8 +30,7 @@ const PROFILE_CONNECTION_OPTIONS: (&[&str], &[&str]) = ( const NON_EMPTY_MESSAGE: &str = "dbt_profile connection values must be non-empty strings"; pub(crate) fn profile_error(py: Python<'_>, message: String) -> PyErr { - match imported(py, DBT_ERRORS_MODULE, "DbtProfileError") - .and_then(|class| class.call1((message,))) + match host::entry(py, ThirdPartyHost::DbtProfileError).and_then(|class| class.call1((message,))) { Ok(instance) => PyErr::from_value(instance), Err(error) => error, @@ -91,11 +89,7 @@ fn profile_connection<'py>( let selected: Bound<'py, PyAny> = load_selected_dbt_profile_output(&profiles_dir, &profile_name, target_name.as_deref())?; let resolved: Bound<'py, PyAny> = call_with( - &imported( - py, - PROFILE_RENDER_MODULE, - "render_selected_dbt_profile_output", - )?, + &host::entry(py, ThirdPartyHost::RenderSelectedDbtProfileOutput)?, &[ ("selected", selected), ("project_dir", dbt_project_dir), @@ -104,7 +98,7 @@ fn profile_connection<'py>( ], )?; call_with( - &imported(py, NATIVE_MODULE, "normalize_dbt_profile_connection")?, + ®istry::native(py, NativeAttr::NormalizeDbtProfileConnection)?, &[("resolved", resolved)], ) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/profile_load.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/profile_load.rs index 83a7dc39ea..60090e0373 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/profile_load.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/profile_load.rs @@ -13,9 +13,11 @@ use sqlbuild_config::dbt_profile::models::{ use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; use crate::bindings::_helpers::dbt_interop::profile::raised; use crate::bindings::_helpers::dbt_interop::profile_connection::profile_error; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::third_party::ThirdPartyHost; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const PROFILE_LOAD_MODULE: &str = "sqlbuild.integrations.dbt._helpers.profile.load"; -const DBT_MODELS: &str = "sqlbuild._native"; const DBT_PROJECT_FILE: &str = "dbt_project.yml"; const PROFILES_FILE: &str = "profiles.yml"; const DEFAULT_PROFILES_DIR: &str = "~/.dbt"; @@ -64,7 +66,7 @@ fn yaml_mapping<'py>(file: &Bound<'py, PyAny>) -> PyResult = call_with( - &imported(py, PROFILE_LOAD_MODULE, "load_dbt_yaml")?, + &host::entry(py, ThirdPartyHost::LoadDbtYaml)?, &[("file_path", file.clone())], )?; dbt_yaml_mapping(&display, &yaml_value(Some(&document))?).map_err(|error| raised(py, error))?; @@ -130,7 +132,7 @@ pub(crate) fn load_selected_dbt_profile_output<'py>( let (target, index): (String, usize) = select_dbt_profile_target(&validated, target_name).map_err(|error| raised(py, error))?; call_with( - &imported(py, DBT_MODELS, "SelectedDbtProfileOutput")?, + ®istry::class(py, RecordId::SelectedDbtProfileOutput)?, &[ ("profile_name", PyString::new(py, profile_name).into_any()), ("target_name", PyString::new(py, &target).into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/reference_resolver.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/reference_resolver.rs index d7803540f0..6eeea47c5c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/reference_resolver.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/reference_resolver.rs @@ -10,11 +10,11 @@ use std::collections::HashSet; use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, keywords, within, }; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; -const LIFECYCLE_MODULE: &str = "sqlbuild.runtime.observability.classes.operation_lifecycle"; const VALIDATE_OPTIONS: (&[&str], &[&str]) = ( &[ "ref_kind", @@ -31,14 +31,13 @@ const SEED_CONFLICT_CODE: &str = "C217"; fn compile_input_error(py: Python<'_>, message: String, code: &str) -> PyErr { let created = || -> PyResult { - let error: Bound<'_, PyAny> = imported(py, COMPILE_ERRORS_MODULE, "CompileInputError")? - .call( - (message,), - Some(&keywords( - py, - &[("code", PyString::new(py, code).into_any())], - )?), - )?; + let error: Bound<'_, PyAny> = registry::error(py, ErrorId::CompileInput)?.call( + (message,), + Some(&keywords( + py, + &[("code", PyString::new(py, code).into_any())], + )?), + )?; Ok(PyErr::from_value(error)) }; created().unwrap_or_else(|failure| failure) @@ -107,7 +106,7 @@ impl DbtCompileReferenceResolver { known_model_names: Bound<'_, PyAny>, ) -> Result<(), PyErr> { call_with( - &imported(py, NATIVE_MODULE, "validate_compile_dbt_model_names")?, + ®istry::native(py, NativeAttr::ValidateCompileDbtModelNames)?, &[ ("known_model_names", known_model_names), ("dbt_manifest", self.manifest_or_none(py)), @@ -258,7 +257,7 @@ impl DbtCompileReferenceResolver { let arguments: KeywordArguments<'_> = KeywordArguments::read(py, "validate_reference", options, VALIDATE_OPTIONS)?; let reference: Bound<'_, PyAny> = call_with( - &imported(py, COMPILE_MODELS_MODULE, "CompileSqlReference")?, + ®istry::class(py, RecordId::CompileSqlReference)?, &[ ("ref_kind", arguments.value("ref_kind")?), ("ref_name", arguments.value("ref_name")?), @@ -266,7 +265,7 @@ impl DbtCompileReferenceResolver { ], )?; call_with( - &imported(py, NATIVE_MODULE, "validate_compile_dbt_model_reference")?, + ®istry::native(py, NativeAttr::ValidateCompileDbtModelReference)?, &[ ("reference", reference), ( @@ -288,7 +287,7 @@ impl DbtCompileReferenceResolver { ref_package: Bound<'py, PyAny>, ) -> PyResult> { call_with( - &imported(py, NATIVE_MODULE, "resolve_dbt_reference_relation")?, + ®istry::native(py, NativeAttr::ResolveDbtReferenceRelation)?, &[ ("manifest", self.manifest_or_none(py)), ("ref_kind", ref_kind), @@ -310,7 +309,7 @@ pub(crate) fn build_compile_reference_resolver<'py>( return Ok(py.None().into_bound(py)); } let manifest: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "build_compile_dbt_manifest_index")?, + ®istry::native(py, NativeAttr::BuildCompileDbtManifestIndex)?, &[("manifest_contents", manifest_contents)], )?; Ok(Bound::new( @@ -358,7 +357,7 @@ pub(crate) fn resolve_external_sql_reference_resolver<'py>( py.None().into_bound(py) } else { let lifecycle: Bound<'py, PyAny> = call_with( - &imported(py, LIFECYCLE_MODULE, "OperationLifecycle")?, + &host::entry(py, ExecutorHost::OperationLifecycle)?, &[ ("operation_kind", PyString::new(py, "project").into_any()), ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/runner.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/runner.rs index 2a17ec7c68..1d69871d7c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/runner.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/runner.rs @@ -20,11 +20,12 @@ use crate::bindings::_helpers::compile_driver::python_calls::{call_with, importe use crate::bindings::_helpers::dbt_interop::cli::{ argv, ls_nodes, resolve_dbt_executable, sequence, }; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::types::CompilerDetach; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const LIFECYCLE_MODULE: &str = "sqlbuild.runtime.observability.classes.operation_lifecycle"; const LAUNCH_FAILURE: &str = "failed to execute dbt"; const POISONED_CACHE: &str = "dbt ls cache is poisoned"; @@ -123,7 +124,7 @@ impl DbtRunner { let result: Bound<'py, PyAny> = self.run(command, &options.getattr("project_dir")?)?; let stdout: String = result.getattr("stdout")?.extract()?; let listing: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DbtLsResult")?, + ®istry::class(py, RecordId::DbtLsResult)?, &[ ("nodes", ls_nodes(py, &stdout)?.into_any()), ("command", result), @@ -191,7 +192,7 @@ impl DbtRunner { }); let output: DbtProcessOutput = output.map_err(|error| launch_failure(py, error))?; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DbtCommandResult")?, + ®istry::class(py, RecordId::DbtCommandResult)?, &[ ("argv", command.into_any()), ("returncode", output.returncode.into_pyobject_any(py)?), @@ -219,7 +220,7 @@ pub(crate) fn dbt_command_lifecycle(py: Python<'_>) -> PyResult options.set_item("operation_kind", "subprocess")?; options.set_item("operation_name", "dbt_command")?; options.set_item("auto_fail_base_exceptions", false)?; - imported(py, LIFECYCLE_MODULE, "OperationLifecycle")?.call((), Some(&options)) + host::entry(py, ExecutorHost::OperationLifecycle)?.call((), Some(&options)) } /// Complete a zero exit, or fail with the exit code and any signal number. @@ -282,9 +283,8 @@ pub(crate) fn launch_failure(py: Python<'_>, error: PyErr) -> PyErr { let created = || -> PyResult { let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("help", error.value(py).str()?)?; - let instance: Bound<'_, PyAny> = - imported(py, NATIVE_RECORDS_MODULE, "DbtInteropRuntimeError")? - .call((LAUNCH_FAILURE,), Some(&options))?; + let instance: Bound<'_, PyAny> = registry::error(py, ErrorId::DbtInteropRuntime)? + .call((LAUNCH_FAILURE,), Some(&options))?; let raised: PyErr = PyErr::from_value(instance); raised.set_cause(py, Some(error.clone_ref(py))); Ok(raised) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/runtime_config.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/runtime_config.rs index 6236e085bd..d707c38bb0 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/runtime_config.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/runtime_config.rs @@ -12,16 +12,15 @@ use sqlbuild_core::dbt_cli::errors::DbtCliError; use sqlbuild_core::dbt_cli::main::dbt_flag_value::dbt_flag_value; use sqlbuild_core::dbt_cli::models::DbtParsedFlags; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::dbt_interop::cli::{raised, resolved_path}; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const BUILTIN_ADAPTERS_MODULE: &str = "sqlbuild.adapter.discovery.main.builtins"; -const PROJECT_ADAPTERS_MODULE: &str = "sqlbuild.adapter.discovery.main.project_adapters"; -const ARGUMENT_ERROR: &str = "DbtInteropArgumentError"; -const CONFIG_ERROR: &str = "DbtInteropConfigError"; +const ARGUMENT_ERROR: ErrorId = ErrorId::DbtInteropArgument; +const CONFIG_ERROR: ErrorId = ErrorId::DbtInteropConfig; const ARGUMENT_CODE: &str = "C238"; const UNRECOGNIZED_HELP: &str = "SQLBuild flags go before `--`; pass raw dbt flags after `--`."; const PARSER_HELP: &str = "Run `-h` to list supported flags; pass raw dbt flags after `--`."; @@ -105,7 +104,7 @@ pub(crate) fn parsed_flags( fn parsed_args_model<'py>(py: Python<'py>, flags: DbtParsedFlags) -> PyResult> { call_with( - &imported(py, NATIVE_MODULE, "DbtInteropParsedArgs")?, + ®istry::class(py, RecordId::DbtInteropParsedArgs)?, &[ ("select", PyTuple::new(py, flags.select)?.into_any()), ("exclude", PyTuple::new(py, flags.exclude)?.into_any()), @@ -185,7 +184,7 @@ pub(crate) fn config_overrides<'py>( ) -> PyResult> { let value = |name: &str| optional_text(py, dbt_flag_value(args, name)); call_with( - &imported(py, NATIVE_MODULE, "DbtCliConfigOverrides")?, + ®istry::class(py, RecordId::DbtCliConfigOverrides)?, &[ ("project_dir", value("--project-dir")), ("profiles_dir", value("--profiles-dir")), @@ -266,7 +265,7 @@ pub(crate) fn plan_options<'py>( let local_config: Bound<'py, PyAny> = discovered_inputs.getattr("local_config")?.getattr("dbt")?; let resolved: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "resolve_dbt_config")?, + ®istry::native(py, NativeAttr::ResolveDbtConfig)?, &[ ("project_root", project_dir.clone()), ("config", project_config.clone()), @@ -285,7 +284,7 @@ pub(crate) fn plan_options<'py>( )? }; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DbtCliOptions")?, + ®istry::class(py, RecordId::DbtCliOptions)?, &[ ("project_dir", resolved.getattr("project_dir")?), ("profiles_dir", resolved.getattr("profiles_dir")?), @@ -321,10 +320,9 @@ pub(crate) fn interop_adapter<'py>( project_dir: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = adapter_name.py(); - let builtin: Bound<'py, PyDict> = - imported(py, BUILTIN_ADAPTERS_MODULE, "builtin_adapter_classes")? - .call0()? - .cast_into()?; + let builtin: Bound<'py, PyDict> = host::entry(py, AdapterHost::BuiltinAdapterClasses)? + .call0()? + .cast_into()?; let mut adapter_class: Option> = None; if !project_dir.is_none() { let reserved: Bound<'py, PyAny> = py @@ -332,7 +330,7 @@ pub(crate) fn interop_adapter<'py>( .getattr("frozenset")? .call1((builtin.clone(),))?; let local: Bound<'py, PyDict> = call_with( - &imported(py, PROJECT_ADAPTERS_MODULE, "discover_project_adapters")?, + &host::entry(py, AdapterHost::DiscoverProjectAdapters)?, &[ ("project_dir", project_dir.clone()), ("reserved_names", reserved), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/runtime_execution.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/runtime_execution.rs index bf09afd152..704d2a8e74 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/runtime_execution.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/runtime_execution.rs @@ -1,9 +1,9 @@ //! Execute dbt through the subprocess boundary and finalize results in artifact order. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::dbt_interop::runtime_results::{append_missing, result_ids}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyInt, PyString, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; @@ -39,14 +39,14 @@ fn expected_total<'py>( ) -> PyResult> { let py = argv.py(); let ls = call_with( - &imported(py, "sqlbuild._native", "dbt_execution_ls_argv")?, + ®istry::native(py, NativeAttr::DbtExecutionLsArgv)?, &[("argv", argv.clone())], )?; if ls.is_none() { return Ok(py.None().into_bound(py)); } let status = call_with( - &imported(py, "sqlbuild._native", "TransientStatusReporter")?, + ®istry::native(py, NativeAttr::TransientStatusReporter)?, &[ ("stream", arguments.value("progress_stream")?), ("use_color", arguments.value("use_color")?), @@ -83,7 +83,7 @@ fn expected_total<'py>( return Ok(py.None().into_bound(py)); } let nodes = call_with( - &imported(py, "sqlbuild._native", "parse_dbt_ls_json_lines")?, + ®istry::native(py, NativeAttr::ParseDbtLsJsonLines)?, &[("stdout", result.getattr("stdout")?)], )?; Ok(PyInt::new(py, nodes.len()?).into_any()) @@ -108,10 +108,10 @@ fn execute_dbt_commands<'py>( let argv = arguments.value("merged_argv")?; let output = arguments.value("progress_stream")?; let style = call_with( - &imported(py, "sqlbuild._native", "CliStyle")?, + ®istry::native(py, NativeAttr::CliStyle)?, &[("use_color", arguments.value("use_color")?)], )?; - let result_type = imported(py, "sqlbuild._native", "DbtCommandExecutionResult")?; + let result_type = registry::class(py, RecordId::DbtCommandExecutionResult)?; if argv.is_none() { let message = arguments.value("skip_message")?; let message = if message.is_none() { @@ -174,7 +174,7 @@ fn execute_dbt_commands<'py>( target = project_dir.call_method1("__truediv__", ("target",))?; } let (returncode, streamed): (Bound<'py, PyAny>, Bound<'py, PyAny>) = call_with( - &imported(py, "sqlbuild._native", "execute_dbt_json_event_stream")?, + ®istry::native(py, NativeAttr::ExecuteDbtJsonEventStream)?, &[ ("argv", argv), ("cwd", project_dir), @@ -211,7 +211,7 @@ fn execute_dbt_commands<'py>( let count = results.len()? - appended.len(); for (offset, result) in appended.into_iter().enumerate() { let rendered = call_with( - &imported(py, "sqlbuild._native", "render_dbt_node_result")?, + ®istry::native(py, NativeAttr::RenderDbtNodeResult)?, &[ ("result", result.clone()), ("use_color", style.getattr("use_color")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/runtime_results.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/runtime_results.rs index b49aeecd87..92af81bd67 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/runtime_results.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/runtime_results.rs @@ -1,6 +1,8 @@ //! Backfill streamed dbt results using Python's JSON and UTF-8 artifact contract. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::exceptions::PyOSError; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::types::{ @@ -8,10 +10,6 @@ use pyo3::types::{ }; use std::collections::HashSet; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_RECORDS: &str = "sqlbuild._native"; - pub(crate) fn result_ids(results: &Bound<'_, PyAny>) -> PyResult> { results .try_iter()? @@ -50,7 +48,7 @@ fn messages<'py>(raw: &Bound<'py, PyDict>) -> PyResult> { PyTuple::new( py, [call_with( - &imported(py, NATIVE_RECORDS, "DbtNodeMessage")?, + ®istry::class(py, RecordId::DbtNodeMessage)?, &[ ("level", PyString::new(py, "error").into_any()), ("message", text), @@ -130,7 +128,7 @@ pub(crate) fn append_missing<'py>( py.None().into_bound(py) }; appended.push(call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DbtNodeExecutionResult")?, + ®istry::class(py, RecordId::DbtNodeExecutionResult)?, &[ ("unique_id", unique_id), ("resource_type", PyString::new(py, resource_type).into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/sqlbuild_work.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/sqlbuild_work.rs index 2a12498023..ab53114c3d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/sqlbuild_work.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/sqlbuild_work.rs @@ -11,9 +11,11 @@ use sqlbuild_core::build_command::constants::{ use crate::bindings::_helpers::build_command::support::{ boolean, emit, joined, keyword_relay, none, text, tuple, }; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; const WORK_OPTIONS: (&[&str], &[&str]) = ( &[ @@ -26,14 +28,12 @@ const WORK_OPTIONS: (&[&str], &[&str]) = ( ], &[], ); -const BUILD_MODELS: &str = "sqlbuild.executor.build.models"; -const NATIVE_RECORDS: &str = "sqlbuild._native"; /// Interop has no allow flags; destructive work uses the ordinary interactive safety gates. #[pyfunction] fn dbt_interop_plan_safety_gates(py: Python<'_>) -> PyResult> { call_with( - &imported(py, NATIVE_RECORDS, "PlanSafetyGates")?, + ®istry::class(py, RecordId::PlanSafetyGates)?, &[ ("allow_missing_migration_origin", boolean(py, false)), ("allow_snapshot_full_refresh", boolean(py, false)), @@ -77,7 +77,7 @@ fn execute_dbt_sqlbuild_work<'py>( let output = context.getattr("output_stream")?; let color = context.getattr("use_color")?; call_with( - &imported(py, "sqlbuild._native", "enforce_plan_safety_policies")?, + ®istry::native(py, NativeAttr::EnforcePlanSafetyPolicies)?, &[ ("plan", plan.clone()), ("gates", dbt_interop_plan_safety_gates(py)?), @@ -91,7 +91,7 @@ fn execute_dbt_sqlbuild_work<'py>( ], )?; let callbacks = call_with( - &imported(py, "sqlbuild._native", "BuildProgressCallbacks")?, + ®istry::native(py, NativeAttr::BuildProgressCallbacks)?, &[ ("plan", plan.clone()), ("use_color", color.clone()), @@ -100,7 +100,7 @@ fn execute_dbt_sqlbuild_work<'py>( ], )?; let command = args.value("command")?; - let kinds = imported(py, "sqlbuild._native", "DbtInteropCommand")?; + let kinds = registry::class(py, RecordId::DbtInteropCommand)?; let display = if command.eq(kinds.getattr("RUN")?)? { "sqb build --no-tests --no-audits".to_owned() } else { @@ -108,7 +108,7 @@ fn execute_dbt_sqlbuild_work<'py>( }; let concurrency = project.getattr("settings")?.getattr("concurrency")?; let header = call_with( - &imported(py, "sqlbuild._native", "format_execution_header")?, + ®istry::native(py, NativeAttr::FormatExecutionHeader)?, &[ ("command", text(py, &display)), ("target", none(py)), @@ -116,7 +116,7 @@ fn execute_dbt_sqlbuild_work<'py>( ], )?; let style = call_with( - &imported(py, "sqlbuild._native", "CliStyle")?, + ®istry::native(py, NativeAttr::CliStyle)?, &[("use_color", color.clone())], )?; emit( @@ -132,7 +132,7 @@ fn execute_dbt_sqlbuild_work<'py>( ), )?; let connection = call_with( - &imported(py, "sqlbuild._native", "ConnectionProgressReporter")?, + ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, &[ ("adapter_name", context.getattr("adapter_name")?), ("stream", output.clone()), @@ -142,7 +142,7 @@ fn execute_dbt_sqlbuild_work<'py>( )?; let build = command.eq(kinds.getattr("BUILD")?)?; let runtime = call_with( - &imported(py, BUILD_MODELS, "BuildRuntimeParams")?, + &host::entry(py, ExecutorHost::BuildRuntimeParams)?, &[ ("run_id", project.getattr("run_id")?), ("run_tests", boolean(py, build)), @@ -153,7 +153,7 @@ fn execute_dbt_sqlbuild_work<'py>( ], )?; let hooks = call_with( - &imported(py, BUILD_MODELS, "BuildCallbacks")?, + &host::entry(py, ExecutorHost::BuildCallbacks)?, &[ ( "on_node_start", @@ -185,11 +185,7 @@ fn execute_dbt_sqlbuild_work<'py>( ], )?; let result = call_with( - &imported( - py, - "sqlbuild.executor.pipeline.main.run", - "run_build_pipeline", - )?, + &host::entry(py, ExecutorHost::RunBuildPipeline)?, &[ ("plan", plan.clone()), ("connection_config", context.getattr("connection_config")?), @@ -200,7 +196,7 @@ fn execute_dbt_sqlbuild_work<'py>( ], )?; call_with( - &imported(py, "sqlbuild._native", "write_runtime_target")?, + ®istry::native(py, NativeAttr::WriteRuntimeTarget)?, &[ ( "target_dir", @@ -211,7 +207,7 @@ fn execute_dbt_sqlbuild_work<'py>( ], )?; let footer = call_with( - &imported(py, "sqlbuild._native", "format_build_footer")?, + ®istry::native(py, NativeAttr::FormatBuildFooter)?, &[ ("result", result.clone()), ("elapsed", callbacks.getattr("elapsed")?), @@ -220,7 +216,7 @@ fn execute_dbt_sqlbuild_work<'py>( )?; emit(&output, &format!("\n{}\n", footer.extract::()?))?; Ok(i64::from(!result.getattr("status")?.eq( - imported(py, "sqlbuild.executor.build.types", "BuildStatus")?.getattr("SUCCESS")?, + host::entry(py, ExecutorHost::BuildStatus)?.getattr("SUCCESS")?, )?)) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/debug_command/run_debug.rs b/crates/sqlbuild-python/src/bindings/_helpers/debug_command/run_debug.rs index de3f8f48f1..fd439a288e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/debug_command/run_debug.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/debug_command/run_debug.rs @@ -34,10 +34,9 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, within, }; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; -const CONTRACT_TYPES_MODULE: &str = "sqlbuild._native"; const DEBUG_OPTIONS: (&[&str], &[&str]) = ( &[], &[ @@ -119,7 +118,7 @@ fn warehouse_checks( selected_target: &context.selected_target, }; let mut checks: Vec = Vec::new(); - for group in imported(py, CONTRACT_TYPES_MODULE, "WarehouseGroup")?.try_iter()? { + for group in registry::class(py, RecordId::WarehouseGroup)?.try_iter()? { let group: Bound<'_, PyAny> = group?; let group_name: String = group.getattr("value")?.str()?.extract()?; if !supports_session_warehouse { @@ -258,12 +257,12 @@ fn build_report( let project_dir_object: Bound<'_, PyAny> = imported(py, "pathlib", "Path")?.call1((project_dir,))?; let discovered_inputs: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[("project_dir", project_dir_object.clone())], )?; let files: DebugConfigFiles = debug_config_files(project_dir); let adapter_name: Bound<'_, PyAny> = call_with( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ( "project_config", @@ -293,7 +292,7 @@ fn build_report( let connection_config: Bound<'_, PyDict> = effective_connection(&context, (&adapter_name, &base_connection))?.cast_into()?; let namespace: Bound<'_, PyTuple> = call_with( - &imported(py, NATIVE_MODULE, "build_effective_target_namespace")?, + ®istry::native(py, NativeAttr::BuildEffectiveTargetNamespace)?, &[ ("discovered_inputs", context.discovered_inputs.clone()), ("selected_target", context.selected_target.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/diagnostics/log_records.rs b/crates/sqlbuild-python/src/bindings/_helpers/diagnostics/log_records.rs index efe696e343..9e207eb743 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/diagnostics/log_records.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/diagnostics/log_records.rs @@ -24,11 +24,10 @@ use sqlbuild_core::redaction::main::redact_text::redact_text; use crate::bindings::_helpers::boundary::keyword_request::Request; use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::diagnostics::redaction::redacted_value; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; -const PRIVATE_SQL_MARKER: (&str, &str) = ( - "sqlbuild.diagnostics.constants", - "SQL_PRIVATE_RECORD_MARKER", -); +const PRIVATE_SQL_MARKER: ExecutorHost = ExecutorHost::SqlPrivateRecordMarker; /// `context | fields | identity`, with later mappings overriding earlier keys in place. fn merged<'py>( @@ -315,7 +314,7 @@ fn compute_diagnostic_log_fields<'py>( return Ok(None); } let trusted_private_sql: bool = match values.get_item(SQL_PRIVATE_RECORD_FIELD)? { - Some(marker) => marker.is(&imported(py, PRIVATE_SQL_MARKER.0, PRIVATE_SQL_MARKER.1)?), + Some(marker) => marker.is(&host::entry(py, PRIVATE_SQL_MARKER)?), None => false, }; let mut identities: Vec<(&str, Option)> = Vec::new(); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/diagnostics/trackers.rs b/crates/sqlbuild-python/src/bindings/_helpers/diagnostics/trackers.rs index 1ca3d76d6d..d1455f1450 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/diagnostics/trackers.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/diagnostics/trackers.rs @@ -8,10 +8,11 @@ use pyo3::types::{PyDict, PyDictMethods, PyTuple, PyType}; use pyo3::{pyclass, pymethods}; use crate::bindings::_helpers::compile_driver::python_calls::{context_variable, imported}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const DIAGNOSTICS_MODELS_MODULE: &str = "sqlbuild.diagnostics.models"; -const PROCESS_RESOURCES_MODULE: &str = "sqlbuild.diagnostics._helpers.process_resources"; const TRACKER_VARIABLE: &str = "sqlbuild_build_phase_timing_tracker"; const PHASES: [&str; 6] = [ "compile_seconds", @@ -101,7 +102,7 @@ impl BuildPhaseTimingTracker { keywords.set_item(phase, value)?; } keywords.set_item("total_seconds", non_negative(now, self.started_at))?; - imported(py, NATIVE_MODULE, "PartialBuildPhaseTimings")?.call((), Some(&keywords)) + registry::class(py, RecordId::PartialBuildPhaseTimings)?.call((), Some(&keywords)) } /// Return the active build timing tracker. @@ -171,7 +172,7 @@ impl ProcessResourceTracker { let monotonic: Bound<'_, PyAny> = clock(py, monotonic)?; let resource_reader: Bound<'_, PyAny> = match resource_reader { Some(reader) => reader, - None => imported(py, PROCESS_RESOURCES_MODULE, "read_process_resources")?, + None => host::entry(py, ExecutorHost::ReadProcessResources)?, }; let started_at: f64 = monotonic.call0()?.extract()?; let (started_user, started_system, _): (f64, f64, Bound<'_, PyAny>) = @@ -201,7 +202,7 @@ impl ProcessResourceTracker { non_negative(system_cpu, self.started_system), )?; keywords.set_item("max_rss_bytes", max_rss_bytes)?; - imported(py, DIAGNOSTICS_MODELS_MODULE, "ProcessResourceUsage")?.call((), Some(&keywords)) + host::entry(py, ExecutorHost::ProcessResourceUsage)?.call((), Some(&keywords)) } } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/diff_report/command.rs b/crates/sqlbuild-python/src/bindings/_helpers/diff_report/command.rs index a9a614ba8e..e9a96b89df 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/diff_report/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/diff_report/command.rs @@ -24,29 +24,23 @@ use crate::bindings::_helpers::diff_report::query::{ }; use crate::bindings::_helpers::diff_report::registration::publish_diff_output; use crate::bindings::_helpers::diff_report::support::{ - EXCEPTIONS_MODULE, call, caused, cli_error, error_text, is_exception, matches_class, none, - object, request_facts, user_error_parts, + call, caused, cli_error, error_text, is_exception, matches_class, matches_native, none, object, + request_facts, user_error_parts, }; use crate::bindings::_helpers::discovery::configuration::discover_project_configuration; use crate::bindings::_helpers::frontier::encoder::emit_error; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const REJECT_UNIT_TESTS_MODULE: &str = "sqlbuild._native"; -const TARGET_NAME_MODULE: &str = "sqlbuild._native"; -const TARGET_CONFIG_MODULE: &str = "sqlbuild._native"; const DEFAULT_DIFF_MAX_FULL_ROWS: i64 = 10_000_000; -const EXTERNAL_REFS_MODULE: &str = "sqlbuild._native"; -const EXECUTE_MODULE: &str = "sqlbuild.executor.diff.main.execute"; -const DIFF_MODELS_MODULE: &str = "sqlbuild.executor.diff.models"; -const ADAPTER_ERRORS: (&str, &str) = ("sqlbuild.adapter.contract.exceptions", "AdapterUserError"); -const EXECUTOR_INPUT_ERRORS: (&str, &str) = - ("sqlbuild.errors.contracts.exceptions", "ExecutorInputError"); -const SIZE_GUARD_ERRORS: (&str, &str) = ( - "sqlbuild.executor.diff.exceptions", - "FullDiffSizeGuardError", -); -const OUTCOME_ERRORS: (&str, &str) = (EXCEPTIONS_MODULE, "QueryDiffOutcomeError"); -const USER_ERRORS: (&str, &str) = (EXCEPTIONS_MODULE, "CliUserError"); +const ADAPTER_ERRORS: AdapterHost = AdapterHost::AdapterUserError; +const EXECUTOR_INPUT_ERRORS: ExecutorHost = ExecutorHost::ExecutorInputError; +const SIZE_GUARD_ERRORS: ExecutorHost = ExecutorHost::FullDiffSizeGuardError; +const OUTCOME_ERRORS: ErrorId = ErrorId::QueryDiffOutcome; +const USER_ERRORS: ErrorId = ErrorId::CliUser; const ROW_DIFF_KEY_ERROR_PREFIX: &str = "row diff "; const ROW_DIFF_KEY_ERROR_FRAGMENT: &str = " unique_key values"; @@ -60,7 +54,7 @@ fn user(message: &str, code: &str, help: Option<&str>) -> DiffUserError { /// `CliUserError` from a pure rejection. fn rejected(py: Python<'_>, error: &DiffUserError) -> PyErr { - cli_error(py, "CliUserError", error) + cli_error(py, ErrorId::CliUser, error) } /// The project directory and discovered inputs, after the request's flags validate. @@ -74,9 +68,9 @@ fn resolve_invocation<'py>( return Err(cli_error( py, if query_mode { - "QueryDiffIncompleteError" + ErrorId::QueryDiffIncomplete } else { - "CliUserError" + ErrorId::CliUser }, &error, )); @@ -89,12 +83,12 @@ fn resolve_invocation<'py>( discover_project_configuration(py, &project_dir)? } else { call( - &imported(py, NATIVE_MODULE, "discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[("project_dir", &project_dir)], )? }; call( - &imported(py, REJECT_UNIT_TESTS_MODULE, "reject_unit_test_selectors")?, + ®istry::native(py, NativeAttr::RejectUnitTestSelectors)?, &[ ("select", &request.getattr("select")?), ("exclude", &request.getattr("exclude")?), @@ -117,10 +111,10 @@ fn query_outcome_error( parts: &DiffUserError, cause: PyErr, ) -> PyErr { - let class: &str = if incomplete { - "QueryDiffIncompleteError" + let class: ErrorId = if incomplete { + ErrorId::QueryDiffIncomplete } else { - "QueryDiffExecutionError" + ErrorId::QueryDiffExecution }; caused(py, cli_error(py, class, parts), cause) } @@ -132,7 +126,7 @@ fn execution_failure(py: Python<'_>, code: &str, cause: PyErr) -> PyResult, error: PyErr) -> PyResult { - if matches_class(py, &error, OUTCOME_ERRORS)? || !is_exception(py, &error) { + if matches_native(py, &error, OUTCOME_ERRORS)? || !is_exception(py, &error) { return Ok(error); } let value: Bound<'_, PyAny> = error.value(py).clone().into_any(); @@ -152,7 +146,7 @@ fn query_execution_error(py: Python<'_>, error: PyErr) -> PyResult { }; return Ok(query_outcome_error(py, true, &parts, error)); } - if matches_class(py, &error, USER_ERRORS)? { + if matches_native(py, &error, USER_ERRORS)? { let parts: DiffUserError = user_error_parts(&value)?; let incomplete: bool = is_incomplete_query_failure(QueryDiffPhase::Execution, &parts.code); return Ok(query_outcome_error(py, incomplete, &parts, error)); @@ -173,7 +167,7 @@ fn run_query_diff( let py: Python<'_> = request.py(); let preparation: Bound<'_, PyAny> = match prepare_query_diff(request, invocation) { Ok(preparation) => preparation, - Err(error) if matches_class(py, &error, USER_ERRORS)? => { + Err(error) if matches_native(py, &error, USER_ERRORS)? => { let parts: DiffUserError = user_error_parts(&error.value(py).clone().into_any())?; let incomplete: bool = is_incomplete_query_failure(QueryDiffPhase::Preparation, &parts.code); @@ -230,7 +224,7 @@ fn target_max_full_rows<'py>( ) -> PyResult> { let py: Python<'py> = discovered.py(); let configured: Bound<'py, PyAny> = call( - &imported(py, TARGET_CONFIG_MODULE, "resolve_target_config")?, + ®istry::native(py, NativeAttr::ResolveTargetConfig)?, &[ ("project_config", &discovered.getattr("project_config")?), ("local_config", &discovered.getattr("local_config")?), @@ -260,7 +254,7 @@ fn diff_to_target<'py>( return Ok(to_name); } let active: Bound<'py, PyAny> = call( - &imported(py, TARGET_NAME_MODULE, "resolve_target_name")?, + ®istry::native(py, NativeAttr::ResolveTargetName)?, &[ ("project_config", &discovered.getattr("project_config")?), ("local_config", &discovered.getattr("local_config")?), @@ -338,7 +332,7 @@ fn size_guard_error( keywords.set_item("details", details)?; keywords.set_item("code", "C270")?; keywords.set_item("help", report.help)?; - let replacement: Bound<'_, PyAny> = imported(py, EXCEPTIONS_MODULE, "DiffSizeGuardError")? + let replacement: Bound<'_, PyAny> = registry::error(py, ErrorId::DiffSizeGuard)? .call((report.message.as_str(),), Some(&keywords))?; Ok(caused(py, PyErr::from_value(replacement), error)) } @@ -378,7 +372,7 @@ fn run_model_diff( let (_, adapter) = project_adapter(discovered, project_dir)?; let cli_vars: Bound<'_, PyAny> = request.getattr("cli_vars")?; let connection_config: Bound<'_, PyAny> = call( - &imported(py, NATIVE_MODULE, "resolve_target_connection_config")?, + ®istry::native(py, NativeAttr::ResolveTargetConnectionConfig)?, &[ ("discovered_inputs", discovered), ("project_dir", project_dir), @@ -387,18 +381,14 @@ fn run_model_diff( ], )?; let resolver: Bound<'_, PyAny> = call( - &imported( - py, - EXTERNAL_REFS_MODULE, - "resolve_external_sql_reference_resolver", - )?, + ®istry::native(py, NativeAttr::ResolveExternalSqlReferenceResolver)?, &[ ("project_dir", project_dir), ("discovered_inputs", discovered), ], )?; let pipeline: Bound<'_, PyTuple> = call( - &imported(py, NATIVE_MODULE, "run_diff_pipeline")?, + ®istry::native(py, NativeAttr::RunDiffPipeline)?, &[ ("discovered_inputs", discovered), ("adapter", &adapter), @@ -431,7 +421,7 @@ fn run_model_diff( none(py) } else { call( - &imported(py, DIFF_MODELS_MODULE, "FullDiffSizeLimits")?, + &host::entry(py, ExecutorHost::FullDiffSizeLimits)?, &[ ("left_target", &from_target), ("right_target", &to_target), @@ -459,7 +449,7 @@ fn run_model_diff( let executed: PyResult> = execution_options(request, &extra).and_then(|options| { call( - &imported(py, EXECUTE_MODULE, "execute_diff")?, + &host::entry(py, ExecutorHost::ExecuteDiff)?, &[ ("adapter", &adapter), ("connection", &connection), @@ -498,7 +488,7 @@ pub(crate) fn run_diff_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> Py Ok(invocation) => invocation, Err(error) if query_mode - && !matches_class(py, &error, OUTCOME_ERRORS)? + && !matches_native(py, &error, OUTCOME_ERRORS)? && is_exception(py, &error) => { return Err(execution_failure(py, "C252", error)?); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/diff_report/query.rs b/crates/sqlbuild-python/src/bindings/_helpers/diff_report/query.rs index 3fed5deff2..7c20b69051 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/diff_report/query.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/diff_report/query.rs @@ -19,27 +19,22 @@ use crate::bindings::_helpers::diff_report::support::{ call, cli_error, error_text, none, object, progress_line, }; use crate::bindings::_helpers::diff_report::tolerances::cli_tolerance_overrides; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::types::CompilerDetach; use sqlbuild_analysis::diff_selection::main::raw_query_shape::raw_query_shape; use sqlbuild_analysis::diff_selection::models::RawQueryShape; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; -const NAMESPACE_MODULE: &str = "sqlbuild._native"; -const RUNTIME_MODULE: &str = "sqlbuild._native"; const GENERIC_DIALECT: &str = "generic"; -const COMMAND_MODELS_MODULE: &str = "sqlbuild._native"; -const LIFECYCLE_MODULE: &str = "sqlbuild.executor.diff.classes.query_artifact_lifecycle"; -const RELATION_LOOKUP_MODULE: &str = "sqlbuild.adapter.relations.main.relation_lookup"; -const CURSOR_MODELS_MODULE: &str = "sqlbuild._native"; -const EXECUTE_QUERY_MODULE: &str = "sqlbuild.executor.diff.main._execute_query"; -const DIFF_MODELS_MODULE: &str = "sqlbuild.executor.diff.models"; const NAMESPACELESS_ADAPTERS: [&str; 2] = ["duckdb", "motherduck"]; fn failure(py: Python<'_>, message: String, code: &str, help: Option<&str>) -> PyErr { cli_error( py, - "CliUserError", + ErrorId::CliUser, &DiffUserError { message, code: code.to_owned(), @@ -49,7 +44,7 @@ fn failure(py: Python<'_>, message: String, code: &str, help: Option<&str>) -> P } fn user_failure(py: Python<'_>, error: DiffUserError) -> PyErr { - cli_error(py, "CliUserError", &error) + cli_error(py, ErrorId::CliUser, &error) } /// The adapter of the effective adapter name, resolved for the project directory. @@ -59,7 +54,7 @@ pub(crate) fn project_adapter<'py>( ) -> PyResult<(String, Bound<'py, PyAny>)> { let py: Python<'py> = discovered.py(); let name: Bound<'py, PyAny> = call( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", &discovered.getattr("project_config")?), ("local_config", &discovered.getattr("local_config")?), @@ -175,7 +170,7 @@ pub(crate) fn prepare_query_diff<'py>( let selected_target: Bound<'py, PyAny> = request.getattr("selected_target")?; let cli_vars: Bound<'py, PyAny> = request.getattr("cli_vars")?; let config: Bound<'py, PyAny> = call( - &imported(py, NATIVE_MODULE, "resolve_project_connection_config")?, + ®istry::native(py, NativeAttr::ResolveProjectConnectionConfig)?, &[ ("discovered_inputs", discovered), ("project_dir", project_dir), @@ -189,7 +184,7 @@ pub(crate) fn prepare_query_diff<'py>( config.call_method1("get", ("database",))? }; let namespace: Bound<'py, PyAny> = call( - &imported(py, NAMESPACE_MODULE, "build_effective_target_namespace")?, + ®istry::native(py, NativeAttr::BuildEffectiveTargetNamespace)?, &[ ("discovered_inputs", discovered), ("selected_target", &selected_target), @@ -234,7 +229,7 @@ pub(crate) fn prepare_query_diff<'py>( let left_sql: String = read_only_query(py, &left_sql, "left", &adapter)?; let right_sql: String = read_only_query(py, &right_sql, "right", &adapter)?; let runtime: Bound<'py, PyTuple> = call( - &imported(py, RUNTIME_MODULE, "build_effective_runtime_config")?, + ®istry::native(py, NativeAttr::BuildEffectiveRuntimeConfig)?, &[ ("discovered_inputs", discovered), ("selected_target", &selected_target), @@ -259,7 +254,7 @@ pub(crate) fn prepare_query_diff<'py>( ) }; call( - &imported(py, COMMAND_MODELS_MODULE, "QueryDiffPreparation")?, + ®istry::class(py, RecordId::QueryDiffPreparation)?, &[ ("adapter", &adapter), ("connection_config", &config), @@ -308,7 +303,7 @@ pub(crate) fn execution_options<'py>( let py: Python<'py> = request.py(); let attribute = |name: &str| request.getattr(name); let sampling: Bound<'py, PyAny> = call( - &imported(py, DIFF_MODELS_MODULE, "RowDiffSamplingOverride")?, + &host::entry(py, ExecutorHost::RowDiffSamplingOverride)?, &[ ("row_limit", &attribute("sample_rows")?), ("seed", &attribute("sample_seed")?), @@ -337,7 +332,7 @@ pub(crate) fn execution_options<'py>( ]); pairs.extend_from_slice(extra); call( - &imported(py, DIFF_MODELS_MODULE, "DiffExecutionOptions")?, + &host::entry(py, ExecutorHost::DiffExecutionOptions)?, &pairs, ) } @@ -376,8 +371,7 @@ pub(crate) fn execute_query_diff<'py>( py, &format!("Query diff connection OK ({:.2}s)", phases[0].1), )?; - let lifecycle: Bound<'py, PyAny> = - imported(py, LIFECYCLE_MODULE, "QueryDiffArtifactLifecycle")?; + let lifecycle: Bound<'py, PyAny> = host::entry(py, ExecutorHost::QueryDiffArtifactLifecycle)?; let artifact = |side: &str| -> PyResult> { call( &lifecycle.getattr("build")?, @@ -451,7 +445,7 @@ pub(crate) fn execute_query_diff<'py>( if model.is_none() { return Err(cli_error( py, - "QueryDiffExecutionError", + ErrorId::QueryDiffExecution, &DiffUserError { message: "query diff comparison completed without a result".to_owned(), code: "C256".to_owned(), @@ -460,7 +454,7 @@ pub(crate) fn execute_query_diff<'py>( )); } let result: Bound<'py, PyAny> = call( - &imported(py, DIFF_MODELS_MODULE, "DiffExecutionResult")?, + &host::entry(py, ExecutorHost::DiffExecutionResult)?, &[("model_results", &PyTuple::new(py, [model])?.into_any())], )?; Ok((result, phases)) @@ -538,7 +532,7 @@ fn compare_artifacts<'py>( let compared: PyResult> = execution_options(request, &extra).and_then(|options| { call( - &imported(py, EXECUTE_QUERY_MODULE, "execute_query_diff")?, + &host::entry(py, ExecutorHost::ExecuteQueryDiff)?, &[ ("adapter", &adapter), ("connection", connection), @@ -670,7 +664,7 @@ fn inspect_inputs<'py>( }; let locations: Bound<'py, PyTuple> = PyTuple::new(py, [location(left)?, location(right)?])?; let lookup: Bound<'py, PyAny> = call( - &imported(py, RELATION_LOOKUP_MODULE, "build_relation_lookup")?, + &host::entry(py, AdapterHost::BuildRelationLookup)?, &[ ("adapter", &adapter), ("connection", connection), @@ -698,7 +692,7 @@ fn inspect_inputs<'py>( )); } } - let ttl: Bound<'py, PyAny> = imported(py, CURSOR_MODELS_MODULE, "Duration")? + let ttl: Bound<'py, PyAny> = registry::class(py, RecordId::Duration)? .call_method1("parse", (preparation.getattr("artifact_ttl")?,))?; if ttl.is_none() { return Err(failure( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/diff_report/support.rs b/crates/sqlbuild-python/src/bindings/_helpers/diff_report/support.rs index 5d8f4c05a9..36d8f85712 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/diff_report/support.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/diff_report/support.rs @@ -7,8 +7,10 @@ use sqlbuild_cli::diff_report::errors::DiffUserError; use sqlbuild_cli::diff_report::models::DiffRequestFacts; use crate::bindings::_helpers::compile_driver::python_calls::imported; - -pub(crate) const EXCEPTIONS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::models::HostId; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; /// `name(**pairs)` with keyword arguments. pub(crate) fn call<'py>( @@ -34,13 +36,13 @@ pub(crate) fn object<'py, T: pyo3::IntoPyObjectExt<'py>>( } /// `class(message, code=code, help=help)` from the CLI exceptions module. -pub(crate) fn cli_error(py: Python<'_>, class: &str, error: &DiffUserError) -> PyErr { +pub(crate) fn cli_error(py: Python<'_>, class: ErrorId, error: &DiffUserError) -> PyErr { let raised = (|| -> PyResult> { let help: Bound<'_, PyAny> = object(py, error.help.as_deref())?; let keywords: Bound<'_, PyDict> = PyDict::new(py); keywords.set_item("code", error.code.as_str())?; keywords.set_item("help", help)?; - imported(py, EXCEPTIONS_MODULE, class)?.call((error.message.as_str(),), Some(&keywords)) + registry::error(py, class)?.call((error.message.as_str(),), Some(&keywords)) })(); match raised { Ok(instance) => PyErr::from_value(instance), @@ -71,9 +73,14 @@ pub(crate) fn caused(py: Python<'_>, replacement: PyErr, cause: PyErr) -> PyErr pub(crate) fn matches_class( py: Python<'_>, error: &PyErr, - (module, class): (&str, &str), + class: impl Into, ) -> PyResult { - error.value(py).is_instance(&imported(py, module, class)?) + error.value(py).is_instance(&host::entry(py, class)?) +} + +/// Whether `error` is an instance of the native error class `class`. +pub(crate) fn matches_native(py: Python<'_>, error: &PyErr, class: ErrorId) -> PyResult { + error.value(py).is_instance(®istry::error(py, class)?) } /// Whether `error` is an `Exception` rather than a `BaseException` such as an interrupt. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/diff_report/tolerances.rs b/crates/sqlbuild-python/src/bindings/_helpers/diff_report/tolerances.rs index 308f7960de..ddac90a6c2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/diff_report/tolerances.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/diff_report/tolerances.rs @@ -7,10 +7,11 @@ use pyo3::types::{PyBool, PyDict, PyDictMethods, PyFloat, PyInt, PyString}; use pyo3::{IntoPyObjectExt, pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::boundary::keyword_request::Request; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; -const MODELS_MODULE: &str = "sqlbuild.adapter.contract.models"; -const EXCEPTIONS_MODULE: &str = "sqlbuild.errors.contracts.exceptions"; const DECIMAL_MODULE: &str = "decimal"; const ABSOLUTE_KEY: &str = "absolute"; const RELATIVE_KEY: &str = "relative"; @@ -21,7 +22,7 @@ const CLI_TOLERANCE_MESSAGE: &str = /// `ExecutorInputError(message, code=code)`. fn input_error(py: Python<'_>, message: String, code: &str) -> PyErr { - imported(py, EXCEPTIONS_MODULE, "ExecutorInputError") + host::entry(py, ExecutorHost::ExecutorInputError) .and_then(|class| { let keywords: Bound<'_, PyDict> = PyDict::new(py); keywords.set_item("code", code)?; @@ -42,7 +43,7 @@ fn parse_row_diff_tolerances<'py>( fn parsed_tolerances<'py>(raw: &Bound<'py, PyAny>, label: &str) -> PyResult> { let py: Python<'py> = raw.py(); - let class: Bound<'py, PyAny> = imported(py, MODELS_MODULE, "RowDiffTolerances")?; + let class: Bound<'py, PyAny> = host::entry(py, AdapterHost::RowDiffTolerances)?; if raw.is_none() { return class.call0(); } @@ -161,7 +162,7 @@ fn tolerance_rule<'py>(raw: &Bound<'py, PyAny>, label: &str) -> PyResult( &format!("{label}.row_diff_sample_seed"), )?; call_with( - &imported(py, MODELS_MODULE, "RowDiffSampling")?, + &host::entry(py, AdapterHost::RowDiffSampling)?, &[ ("row_limit", row_limit.into_bound_py_any(py)?), ("seed", seed.unwrap_or(0).into_bound_py_any(py)?), @@ -331,7 +332,7 @@ fn merge_row_diff_tolerances<'py>( Ok(combined.into_any()) }; call_with( - &imported(py, MODELS_MODULE, "RowDiffTolerances")?, + &host::entry(py, AdapterHost::RowDiffTolerances)?, &[ ("by_type", merged("by_type")?), ("by_column", merged("by_column")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/configuration.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/configuration.rs index ab63518455..a3364bcffc 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/configuration.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/configuration.rs @@ -3,11 +3,11 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::PyString; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, within}; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const DISCOVERY_MODELS_MODULE: &str = "sqlbuild._native"; -const LIFECYCLE_MODULE: &str = "sqlbuild.runtime.observability.classes.operation_lifecycle"; +use crate::bindings::_helpers::compile_driver::python_calls::{call_with, within}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; /// `DiscoveredProjectInputs` with the loaded configuration, inside the project discovery lifecycle. pub(crate) fn discover_project_configuration<'py>( @@ -15,7 +15,7 @@ pub(crate) fn discover_project_configuration<'py>( project_dir: &Bound<'py, PyAny>, ) -> PyResult> { let lifecycle: Bound<'py, PyAny> = call_with( - &imported(py, LIFECYCLE_MODULE, "OperationLifecycle")?, + &host::entry(py, ExecutorHost::OperationLifecycle)?, &[ ("operation_kind", PyString::new(py, "project").into_any()), ( @@ -26,17 +26,17 @@ pub(crate) fn discover_project_configuration<'py>( )?; within(&lifecycle, |_| { call_with( - &imported(py, DISCOVERY_MODELS_MODULE, "DiscoveredProjectInputs")?, + ®istry::class(py, RecordId::DiscoveredProjectInputs)?, &[ ("project_dir", project_dir.clone()), ( "project_config", - imported(py, NATIVE_MODULE, "load_project_config_file")? + registry::native(py, NativeAttr::LoadProjectConfigFile)? .call1((project_dir,))?, ), ( "local_config", - imported(py, NATIVE_MODULE, "load_local_config_file")?.call1((project_dir,))?, + registry::native(py, NativeAttr::LoadLocalConfigFile)?.call1((project_dir,))?, ), ], ) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/declaration_records.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/declaration_records.rs index 6bc8b68467..1759145de6 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/declaration_records.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/declaration_records.rs @@ -1,6 +1,5 @@ //! Declaration file collections discovered natively and built as Python discovery records. -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class_at; use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; use pyo3::{IntoPyObjectExt, pyfunction, wrap_pyfunction}; @@ -28,16 +27,12 @@ use crate::bindings::_helpers::discovery::snapshot::{ }; use crate::bindings::_helpers::discovery::sorted_keys::sorted_keys; use crate::bindings::_helpers::model_config::header_columns::schema_columns; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sqltext::authored_values::map_to_python; use crate::bindings::models::{NativeDiscoverySession, NativeProjectTree}; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const MODELS_MODULE: &str = "sqlbuild._native"; -const SCOPE_TYPES_MODULE: &str = "sqlbuild._native"; -const AUDITING_MODELS_MODULE: &str = "sqlbuild._native"; const AUDITING_TYPES_MODULE: &str = "sqlbuild.compiler.auditing.types"; -const CONTRACT_MODELS_MODULE: &str = "sqlbuild._native"; const PATHLIB_MODULE: &str = "pathlib"; const SESSION_MEMO_KEY: &str = "native_discovery_session"; const MEASUREMENT_MODE: &str = "MEASUREMENT"; @@ -75,8 +70,8 @@ impl<'py> FileContext<'_, 'py> { } } - fn record(&self, class: &str, fields: &Bound<'py, PyDict>) -> PyResult> { - shape_class_at(self.py(), MODELS_MODULE, class)?.call((), Some(fields)) + fn record(&self, class: RecordId, fields: &Bound<'py, PyDict>) -> PyResult> { + registry::class(self.py(), class)?.call((), Some(fields)) } } @@ -89,7 +84,7 @@ pub(crate) fn add_scope_fields( let py: Python<'_> = fields.py(); fields.set_item( "scope_kind", - imported(py, SCOPE_TYPES_MODULE, "ScopeKind")?.call1((scope.scope_kind,))?, + registry::class(py, RecordId::ScopeKind)?.call1((scope.scope_kind,))?, )?; for (name, path) in [ ("ownership_root", &scope.ownership_root), @@ -132,8 +127,8 @@ fn single_value<'py>( fn enum_file<'py>(context: &FileContext<'_, 'py>, file: &EnumFile) -> PyResult> { let py: Python<'py> = context.py(); - let member_class: Bound<'py, PyAny> = imported(py, NATIVE_RECORDS_MODULE, "EnumMember")?; - let declaration_class: Bound<'py, PyAny> = imported(py, MODELS_MODULE, "EnumDeclaration")?; + let member_class: Bound<'py, PyAny> = registry::class(py, RecordId::EnumMember)?; + let declaration_class: Bound<'py, PyAny> = registry::class(py, RecordId::EnumDeclaration)?; let mut declarations: Vec> = Vec::with_capacity(file.declarations.len()); for declaration in &file.declarations { let members: Vec> = authored(py, &declaration.members)? @@ -156,7 +151,7 @@ fn enum_file<'py>(context: &FileContext<'_, 'py>, file: &EnumFile) -> PyResult = context.fields(&file.contents)?; fields.set_item("declarations", PyTuple::new(py, declarations)?)?; context.add_scope(&fields)?; - context.record("DiscoveredEnumFile", &fields) + context.record(RecordId::DiscoveredEnumFile, &fields) } fn constant_file<'py>( @@ -184,7 +179,7 @@ fn constant_file<'py>( let fields: Bound<'py, PyDict> = context.fields(&file.contents)?; fields.set_item("declarations", PyTuple::new(py, declarations)?)?; context.add_scope(&fields)?; - context.record("DiscoveredConstantFile", &fields) + context.record(RecordId::DiscoveredConstantFile, &fields) } /// Python's `native_locations`: a repeated name keeps its first position and last value. @@ -193,7 +188,7 @@ pub(crate) fn source_locations<'py>( relative_path: &Bound<'py, PyAny>, rows: &[(String, LineColumnSpan)], ) -> PyResult> { - let class: Bound<'py, PyAny> = imported(py, CONTRACT_MODELS_MODULE, "SourceLocation")?; + let class: Bound<'py, PyAny> = registry::class(py, RecordId::SourceLocation)?; let located: Bound<'py, PyDict> = PyDict::new(py); for (name, span) in rows { located.set_item( @@ -220,7 +215,7 @@ fn schema_file<'py>( let py: Python<'py> = context.py(); let file_path: String = context.file_path.str()?.to_string(); let declaration_class: Bound<'py, PyAny> = - imported(py, NATIVE_RECORDS_MODULE, "ModelSchemaDeclaration")?; + registry::class(py, RecordId::ModelSchemaDeclaration)?; let mut declarations: Vec> = Vec::with_capacity(file.declarations.len()); for declaration in &file.declarations { let raw_columns: Bound<'py, PyAny> = @@ -257,7 +252,7 @@ fn schema_file<'py>( let fields: Bound<'py, PyDict> = context.fields(&file.contents)?; fields.set_item("declarations", PyTuple::new(py, declarations)?)?; context.add_scope(&fields)?; - context.record("DiscoveredModelSchemaFile", &fields) + context.record(RecordId::DiscoveredModelSchemaFile, &fields) } fn function_file<'py>( @@ -270,7 +265,7 @@ fn function_file<'py>( authored(context.py(), &file.header_values)?, )?; fields.set_item("body_sql", &file.body_sql)?; - context.record("DiscoveredSqlFunctionFile", &fields) + context.record(RecordId::DiscoveredSqlFunctionFile, &fields) } fn hook_file<'py>(context: &FileContext<'_, 'py>, file: &HookFile) -> PyResult> { @@ -283,7 +278,7 @@ fn hook_file<'py>(context: &FileContext<'_, 'py>, file: &HookFile) -> PyResult(py: Python<'py>, block: &AuditBlock) -> PyResult> { @@ -295,7 +290,7 @@ fn audit_block<'py>(py: Python<'py>, block: &AuditBlock) -> PyResult(py: Python<'py>, block: &AuditBlock) -> PyResult( let declaration_kind: &str = context.scope.map_or("", |scope| scope.declaration_kind); fields.set_item( "declaration_kind", - imported(py, SCOPE_TYPES_MODULE, "DeclarationKind")?.call1((declaration_kind,))?, + registry::class(py, RecordId::DeclarationKind)?.call1((declaration_kind,))?, )?; context.add_scope(&fields)?; - context.record("DiscoveredAuditFile", &fields) + context.record(RecordId::DiscoveredAuditFile, &fields) } fn macro_file<'py>( @@ -363,7 +358,7 @@ fn macro_file<'py>( ) -> PyResult> { let fields: Bound<'py, PyDict> = context.fields(&file.contents)?; context.add_scope(&fields)?; - context.record("DiscoveredMacroFile", &fields) + context.record(RecordId::DiscoveredMacroFile, &fields) } /// The session of this pass, created and memoised by the first declaration collection. @@ -524,7 +519,7 @@ fn seed_records<'py, T>( files: &[ScopedFile], ) -> PyResult> { let py: Python<'py> = faults.project_dir.py(); - let class: Bound<'py, PyAny> = imported(py, NATIVE_RECORDS_MODULE, "DiscoveredSeedFile")?; + let class: Bound<'py, PyAny> = registry::class(py, RecordId::DiscoveredSeedFile)?; let seeds: Vec> = files .iter() .map(|scoped| { @@ -672,7 +667,7 @@ pub(crate) fn discover_adapter_file<'py>( return Ok(py.None().into_bound(py)); } call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DiscoveredAdapterFile")?, + ®istry::class(py, RecordId::DiscoveredAdapterFile)?, &[ ("file_path", file_path), ("relative_path", path_class(py)?.call1(("adapter.py",))?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/description_inputs.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/description_inputs.rs index dc303e0764..2fb0491bd0 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/description_inputs.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/description_inputs.rs @@ -4,11 +4,9 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyRes use pyo3::types::{PyBool, PyCFunction, PyFrozenSet}; use pyo3::{pyfunction, wrap_pyfunction}; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, within}; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const DISCOVERY_MODELS_MODULE: &str = "sqlbuild._native"; -const CONTRACT_MODELS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::compile_driver::python_calls::{call_with, within}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; /// Models and model schemas, skipping files that cannot be read. #[pyfunction] @@ -17,9 +15,9 @@ pub(crate) fn discover_model_description_inputs<'py>( project_dir: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = project_dir.py(); - let native = |name: &str| imported(py, NATIVE_MODULE, name); + let native = |name: NativeAttr| registry::native(py, name); let project_config: Bound<'py, PyAny> = - native("load_project_config_file")?.call1((project_dir,))?; + native(NativeAttr::LoadProjectConfigFile)?.call1((project_dir,))?; let skip: Bound<'py, PyAny> = PyCFunction::new_closure(py, None, None, |_arguments, _keywords| -> PyResult<()> { Ok(()) @@ -27,22 +25,22 @@ pub(crate) fn discover_model_description_inputs<'py>( .into_any(); let disabled: Bound<'py, PyAny> = PyBool::new(py, false).to_owned().into_any(); let scope: Bound<'py, PyAny> = call_with( - &native("directory_snapshot_scope")?, + &native(NativeAttr::DirectorySnapshotScope)?, &[("project_dir", project_dir.clone())], )?; within(&scope, |_| { call_with( - &imported(py, DISCOVERY_MODELS_MODULE, "DiscoveredProjectInputs")?, + ®istry::class(py, RecordId::DiscoveredProjectInputs)?, &[ ("project_config", project_config), ( "local_config", - imported(py, CONTRACT_MODELS_MODULE, "LocalConfig")?.call0()?, + registry::class(py, RecordId::LocalConfig)?.call0()?, ), ( "model_files", call_with( - &native("discover_model_files")?, + &native(NativeAttr::DiscoverModelFiles)?, &[ ("project_dir", project_dir.clone()), ("extract_implicit_alias_columns", disabled.clone()), @@ -54,7 +52,7 @@ pub(crate) fn discover_model_description_inputs<'py>( ( "model_schema_files", call_with( - &native("discover_model_schema_files")?, + &native(NativeAttr::DiscoverModelSchemaFiles)?, &[("project_dir", project_dir.clone()), ("on_fault", skip)], )?, ), @@ -70,9 +68,9 @@ pub(crate) fn externally_described_models<'py>( let py: Python<'py> = project_dir.py(); let inputs: Bound<'py, PyAny> = discover_model_description_inputs(project_dir)?; let schemas: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "public_model_schema_index")?.call1((&inputs,))?; + registry::native(py, NativeAttr::PublicModelSchemaIndex)?.call1((&inputs,))?; let described: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "externally_described_models")?.call1((&inputs, schemas))?; + registry::native(py, NativeAttr::ExternallyDescribedModels)?.call1((&inputs, schemas))?; PyFrozenSet::new(py, described.try_iter()?.collect::>>()?) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/faults.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/faults.rs index 56b7b29f06..dcecb736de 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/faults.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/faults.rs @@ -4,10 +4,10 @@ use pyo3::exceptions::{PyOSError, PySyntaxError, PyUnicodeError, PyValueError}; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python}; use pyo3::types::PyTuple; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; -const FAULT_CLASS: &str = "DiscoveryFileFault"; const PROJECT_DIR_MARKER: &str = "."; /// The files one discovery builds, with the caller's optional `on_fault` reporter. @@ -46,7 +46,7 @@ impl<'py> FileFaults<'py> { .str()? .call_method1("replace", (self.project_dir.str()?, PROJECT_DIR_MARKER))?; on_fault.call1((call_with( - &imported(py, NATIVE_RECORDS_MODULE, FAULT_CLASS)?, + ®istry::class(py, RecordId::DiscoveryFileFault)?, &[("path", relative_path.clone()), ("message", message)], )?,))?; Ok(None) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/header_declarations.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/header_declarations.rs index 08024d074d..d8223255b9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/header_declarations.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/header_declarations.rs @@ -15,30 +15,22 @@ use sqlbuild_model_config::declarations::models::{ use sqlbuild_model_config::sql_values::errors::SqlValueFailure; use sqlbuild_model_config::sql_values::models::RawSqlValue; -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class; use crate::bindings::_helpers::contracts::sql_values::{ SqlValueClasses, raw_value, sql_value, sql_value_error, }; - -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const MODELS_MODULE: &str = "sqlbuild._native"; -const DISCOVERY_ERRORS_MODULE: &str = "sqlbuild._native"; -const IDENTITY_ERRORS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; fn declaration_error(py: Python<'_>, failure: DeclarationFailure) -> PyResult { Ok(match failure { - DeclarationFailure::Parse(message) => PyErr::from_value( - py.import(DISCOVERY_ERRORS_MODULE)? - .getattr("DeclarationParseError")? - .call1((message,))?, - ), + DeclarationFailure::Parse(message) => { + PyErr::from_value(registry::error(py, ErrorId::DeclarationParse)?.call1((message,))?) + } DeclarationFailure::Identity(message, help) => { let keywords = PyDict::new(py); keywords.set_item("help", help)?; PyErr::from_value( - py.import(IDENTITY_ERRORS_MODULE)? - .getattr("ResourceIdentityError")? + registry::error(py, ErrorId::ResourceIdentity)? .call((message,), Some(&keywords))?, ) } @@ -59,8 +51,7 @@ fn enum_record<'py>( facts: EnumFacts, owner: &Owner<'py>, ) -> PyResult> { - let models = py.import(MODELS_MODULE)?; - let member_class = shape_class(models.as_any(), "EnumMember")?; + let member_class = registry::class(py, RecordId::EnumMember)?; let int_class = py.import("builtins")?.getattr("int")?; let members = facts .members @@ -73,7 +64,7 @@ fn enum_record<'py>( member_class.call1((name, value)) }) .collect::>>()?; - models.getattr("EnumDeclaration")?.call1(( + registry::class(py, RecordId::EnumDeclaration)?.call1(( facts.name, PyTuple::new(py, members)?, facts.scalar_type, @@ -89,21 +80,18 @@ fn constant_record<'py>( classes: &SqlValueClasses<'py>, ) -> PyResult> { let render_as = match facts.render_as { - Some(rendering) => py - .import(NATIVE_RECORDS_MODULE)? - .getattr("CollectionRendering")? - .call1((rendering,))?, + Some(rendering) => { + registry::class(py, RecordId::CollectionRendering)?.call1((rendering,))? + } None => py.None().into_bound(py), }; - py.import(MODELS_MODULE)? - .getattr("ConstantDeclaration")? - .call1(( - facts.name, - sql_value(&facts.value, classes)?, - &owner.relative_path, - owner.model_name.as_deref(), - render_as, - )) + registry::class(py, RecordId::ConstantDeclaration)?.call1(( + facts.name, + sql_value(&facts.value, classes)?, + &owner.relative_path, + owner.model_name.as_deref(), + render_as, + )) } fn optional_raw( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/header_values.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/header_values.rs index f0d83ff160..c10008829f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/header_values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/header_values.rs @@ -6,10 +6,9 @@ use pyo3::types::{PyDict, PyDictMethods, PyList, PyListMethods, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::boundary::keyword_request::Request; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const DISCOVERY_ERRORS_MODULE: &str = "sqlbuild._native"; -const CONTRACT_MODELS_MODULE: &str = "sqlbuild._native"; const HEADER_CACHE_SIZE: usize = 4096; /// `(name, line, column, end line, end column)`. @@ -50,10 +49,8 @@ fn prepare_model_header_tokens(headers: &Bound<'_, PyAny>) -> PyResult<()> { cache.clear(); } let names: Bound<'_, PyList> = missing.keys(); - let parsed: Bound<'_, PyAny> = py - .import(NATIVE_MODULE)? - .getattr("parse_model_headers")? - .call1((&names,))?; + let parsed: Bound<'_, PyAny> = + registry::native(py, NativeAttr::ParseModelHeaders)?.call1((&names,))?; for (header, row) in names.iter().zip(parsed.try_iter()?) { let row: Bound<'_, PyAny> = row?; cache.set_item( @@ -83,11 +80,10 @@ fn parse_header_values<'py>(request: Option<&Bound<'py, PyDict>>) -> PyResult = request.get("header_line")?; - let (detail, help, line): (String, Option, Option) = py - .import(NATIVE_MODULE)? - .getattr("model_header_failure")? - .call1((error.str()?, &header, &header_line))? - .extract()?; + let (detail, help, line): (String, Option, Option) = + registry::native(py, NativeAttr::ModelHeaderFailure)? + .call1((error.str()?, &header, &header_line))? + .extract()?; let file_path: String = request.get("file_path")?.str()?.extract()?; let location: String = match line { Some(line) => format!("{file_path}:{line}"), @@ -96,9 +92,7 @@ fn parse_header_values<'py>(request: Option<&Bound<'py, PyDict>>) -> PyResult = match request.optional("error_class")? { Some(class) if !class.is_none() => class, - _ => py - .import(DISCOVERY_ERRORS_MODULE)? - .getattr("ModelSqlParseError")?, + _ => registry::error(py, ErrorId::ModelSqlParse)?, }; let keywords: Bound<'py, PyDict> = PyDict::new(py); keywords.set_item("help", help)?; @@ -117,9 +111,7 @@ fn source_locations<'py>( relative_path: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = rows.py(); - let location_class: Bound<'py, PyAny> = py - .import(CONTRACT_MODELS_MODULE)? - .getattr("SourceLocation")?; + let location_class: Bound<'py, PyAny> = registry::class(py, RecordId::SourceLocation)?; let locations: Bound<'py, PyDict> = PyDict::new(py); for row in rows.try_iter()? { let (name, line, column, end_line, end_column): LocationRow<'py> = row?.extract()?; @@ -146,9 +138,7 @@ fn model_output_column_locations<'py>( Some(value) => value.is_truthy()?, None => true, }; - let rows: Bound<'py, PyAny> = py - .import(NATIVE_MODULE)? - .getattr("model_output_columns")? + let rows: Bound<'py, PyAny> = registry::native(py, NativeAttr::ModelOutputColumns)? .call1((request.get("contents")?, implicit))?; source_locations(&rows, &request.get("relative_path")?) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/hook_entries.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/hook_entries.rs index 10b44186a4..7ee298031a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/hook_entries.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/hook_entries.rs @@ -4,9 +4,9 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyRes use pyo3::types::{PyDict, PyDictMethods, PyList, PyListMethods, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; -use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const DISCOVERY_MODELS_MODULE: &str = "sqlbuild._native"; const RELATIVE_PATH_FIELD: &str = "relative_path"; const SQL_HOOK_FIELDS: [&str; 6] = [ "statement", @@ -47,8 +47,8 @@ pub(crate) fn hook_entries<'py>( if !value.is_instance_of::() && !value.is_instance_of::() { return Ok(hooks); } - let sql_class: Bound<'py, PyAny> = imported(py, DISCOVERY_MODELS_MODULE, "SqlHookEntry")?; - let python_class: Bound<'py, PyAny> = imported(py, DISCOVERY_MODELS_MODULE, "PythonHookEntry")?; + let sql_class: Bound<'py, PyAny> = registry::class(py, RecordId::SqlHookEntry)?; + let python_class: Bound<'py, PyAny> = registry::class(py, RecordId::PythonHookEntry)?; for entry in value.try_iter()? { let entry: Bound<'py, PyAny> = entry?; if entry.is_instance(&sql_class)? { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/imported_modules.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/imported_modules.rs index 5257ca286a..ca782ab0ae 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/imported_modules.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/imported_modules.rs @@ -4,14 +4,12 @@ use pyo3::exceptions::PyException; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyString, PyTuple}; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::event_exporting::scope::current_scope; - -pub(crate) const NATIVE_MODULE: &str = "sqlbuild._native"; -pub(crate) const SHIM_MODULE: &str = - "sqlbuild.compiler.discovery._helpers.filesystem.project_modules"; -pub(crate) const MODELS_MODULE: &str = "sqlbuild._native"; -pub(crate) const ERRORS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; /// One project directory under discovery. pub(crate) struct ProjectDir<'py> { @@ -31,7 +29,7 @@ impl<'py> ProjectDir<'py> { /// `project_relative_path(path=file_path, project_dir=project_dir)`. pub(crate) fn relative(&self, file_path: &Bound<'py, PyAny>) -> PyResult> { call_with( - &imported(self.py(), NATIVE_MODULE, "project_relative_path")?, + ®istry::native(self.py(), NativeAttr::ProjectRelativePath)?, &[ ("path", file_path.clone()), ("project_dir", self.path.clone()), @@ -55,7 +53,7 @@ impl<'py> ProjectDir<'py> { ) -> PyResult>> { let py: Python<'py> = self.py(); call_with( - &imported(py, NATIVE_MODULE, "discover_python_module_files")?, + ®istry::native(py, NativeAttr::DiscoverPythonModuleFiles)?, &[ ("project_dir", self.path.clone()), ("root", PyString::new(py, root).into_any()), @@ -74,7 +72,7 @@ impl<'py> ProjectDir<'py> { /// `shim.(file_path=file_path, project_dir=project_dir)`. pub(crate) fn load( &self, - loader: &str, + loader: UserCodeHost, file_path: &Bound<'py, PyAny>, ) -> PyResult> { call_with( @@ -101,15 +99,15 @@ impl<'py> ProjectDir<'py> { } /// `project_modules.`. -pub(crate) fn shim<'py>(py: Python<'py>, name: &str) -> PyResult> { - imported(py, SHIM_MODULE, name) +pub(crate) fn shim<'py>(py: Python<'py>, name: UserCodeHost) -> PyResult> { + host::entry(py, name) } /// Functions defined in one loaded module. pub(crate) fn module_functions<'py>( module: &Bound<'py, PyAny>, ) -> PyResult>> { - shim(module.py(), "module_functions")? + shim(module.py(), UserCodeHost::ModuleFunctions)? .call1((module,))? .try_iter()? .collect() @@ -118,15 +116,15 @@ pub(crate) fn module_functions<'py>( /// `sqlbuild._native.(**pairs)`. pub(crate) fn discovered<'py>( py: Python<'py>, - class_name: &str, + class_name: RecordId, pairs: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - call_with(&imported(py, MODELS_MODULE, class_name)?, pairs) + call_with(®istry::class(py, class_name)?, pairs) } /// `sqlbuild._native.(message)`. -pub(crate) fn discovery_error(py: Python<'_>, class_name: &str, message: String) -> PyErr { - imported(py, ERRORS_MODULE, class_name) +pub(crate) fn discovery_error(py: Python<'_>, class_name: ErrorId, message: String) -> PyErr { + registry::error(py, class_name) .and_then(|class| class.call1((message,))) .map_or_else(|error| error, PyErr::from_value) } @@ -134,7 +132,7 @@ pub(crate) fn discovery_error(py: Python<'_>, class_name: &str, message: String) /// `discovery_error` raised from `cause`. pub(crate) fn discovery_error_from( py: Python<'_>, - (class_name, message): (&str, String), + (class_name, message): (ErrorId, String), cause: PyErr, ) -> PyErr { let error: PyErr = discovery_error(py, class_name, message); @@ -163,7 +161,7 @@ pub(crate) fn provider_usages<'py>( provider_by_name: &Bound<'py, PyDict>, ) -> PyResult> { call_with( - &shim(function.py(), "provider_usages")?, + &shim(function.py(), UserCodeHost::ProviderUsages)?, &[ ("function", function.clone()), ("provider_by_name", provider_by_name.clone().into_any()), @@ -182,8 +180,8 @@ pub(crate) fn tuple_of<'py>( /// `name` of the read definition, or `None` when the function carries no such definition. pub(crate) fn definition<'py>( function: &Bound<'py, PyAny>, - (module, reader): (&str, &str), + reader: UserCodeHost, ) -> PyResult>> { - let read: Bound<'py, PyAny> = imported(function.py(), module, reader)?.call1((function,))?; + let read: Bound<'py, PyAny> = host::entry(function.py(), reader)?.call1((function,))?; Ok((!read.is_none()).then_some(read)) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/imported_providers.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/imported_providers.rs index 1ea77ab871..4f4232d00a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/imported_providers.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/imported_providers.rs @@ -8,18 +8,19 @@ use pyo3::types::{PyDict, PyDictMethods, PyString}; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; use crate::bindings::_helpers::discovery::imported_modules::{ - NATIVE_MODULE, ProjectDir, discovered, discovery_error, discovery_error_from, module_functions, + ProjectDir, discovered, discovery_error, discovery_error_from, module_functions, provider_by_name, provider_usages, shim, tuple_of, }; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const PROVIDER_ERROR: &str = "ProviderDiscoveryError"; -const SINK_ERROR: &str = "EventExporterDiscoveryError"; -const PROVIDER_INPUT_ERROR: (&str, &str) = ("sqlbuild.provider.exceptions", "ProviderInputError"); +const PROVIDER_ERROR: ErrorId = ErrorId::ProviderDiscovery; +const SINK_ERROR: ErrorId = ErrorId::EventExporterDiscovery; +const PROVIDER_INPUT_ERROR: UserCodeHost = UserCodeHost::ProviderInputError; const VALIDATION_ERROR: (&str, &str) = ("pydantic", "ValidationError"); -const SINK_DEFINITION: (&str, &str) = ( - "sqlbuild.runtime.event_exporting.main.get_lifecycle_event_sink_definition", - "get_lifecycle_event_sink_definition", -); +const SINK_DEFINITION: UserCodeHost = UserCodeHost::GetLifecycleEventSinkDefinition; const ROOT_LOCATION: &str = ""; const DEFAULT_MESSAGE: &str = "invalid value"; const DEFAULT_ERROR_TYPE: &str = "validation_error"; @@ -31,7 +32,7 @@ fn reject_invalid_identity( ) -> PyResult<()> { let py: Python<'_> = name.py(); call_with( - &imported(py, NATIVE_MODULE, "validate_resource_identity")?, + ®istry::native(py, NativeAttr::ValidateResourceIdentity)?, &[ ("name", name.clone()), ("kind", PyString::new(py, "provider").into_any()), @@ -54,8 +55,7 @@ fn provider_name<'py>( let name: Bound<'py, PyAny> = match provider_class.call_method0("name") { Ok(name) => name, Err(error) => { - let input_error: Bound<'py, PyAny> = - imported(py, PROVIDER_INPUT_ERROR.0, PROVIDER_INPUT_ERROR.1)?; + let input_error: Bound<'py, PyAny> = host::entry(py, PROVIDER_INPUT_ERROR)?; if !error.value(py).is_instance(&input_error)? { return Err(error); } @@ -138,7 +138,7 @@ pub(crate) fn discover_provider_classes<'py>( ) -> PyResult> { let py: Python<'py> = project_dir.py(); let project = ProjectDir { path: project_dir }; - let record_read: Bound<'py, PyAny> = imported(py, NATIVE_MODULE, "record_settings_class_read")?; + let record_read: Bound<'py, PyAny> = registry::native(py, NativeAttr::RecordSettingsClassRead)?; if let Some(cached) = project.cached_extensions()? { let providers: Bound<'py, PyAny> = cached.get_item(0)?; for provider in providers.try_iter()? { @@ -156,8 +156,10 @@ pub(crate) fn discover_provider_classes<'py>( let mut providers: Vec> = Vec::new(); let mut seen: Vec<(String, Bound<'py, PyAny>)> = Vec::new(); for file_path in project.module_files("providers", true)? { - let module: Bound<'py, PyAny> = project.load("load_provider_module", &file_path)?; - let classes: Bound<'py, PyAny> = shim(py, "module_provider_classes")?.call1((&module,))?; + let module: Bound<'py, PyAny> = + project.load(UserCodeHost::LoadProviderModule, &file_path)?; + let classes: Bound<'py, PyAny> = + shim(py, UserCodeHost::ModuleProviderClasses)?.call1((&module,))?; for provider_class in classes.try_iter()? { let provider_class: Bound<'py, PyAny> = provider_class?; let relative_path: Bound<'py, PyAny> = project.relative(&file_path)?; @@ -180,7 +182,7 @@ pub(crate) fn discover_provider_classes<'py>( provider_instance(&provider_class, (&name, &relative_path))?; providers.push(discovered( py, - "DiscoveredProvider", + RecordId::DiscoveredProvider, &[ ("file_path", file_path.clone()), ("relative_path", relative_path), @@ -220,11 +222,11 @@ pub(crate) fn discover_event_exporter_declarations<'py>( { return tuple_of(py, Vec::new()); } - let read_definition: Bound<'py, PyAny> = imported(py, SINK_DEFINITION.0, SINK_DEFINITION.1)?; + let read_definition: Bound<'py, PyAny> = host::entry(py, SINK_DEFINITION)?; let mut declarations: Vec> = Vec::new(); let mut seen: Vec<(String, Bound<'py, PyAny>)> = Vec::new(); for file_path in project.module_files("sinks", true)? { - let module: Bound<'py, PyAny> = project.load("load_sink_module", &file_path)?; + let module: Bound<'py, PyAny> = project.load(UserCodeHost::LoadSinkModule, &file_path)?; let mut seen_functions: HashSet = HashSet::new(); for function in module_functions(&module)? { if !seen_functions.insert(function.as_ptr() as usize) { @@ -248,7 +250,7 @@ pub(crate) fn discover_event_exporter_declarations<'py>( } let relative_path: Bound<'py, PyAny> = project.relative(&file_path)?; call_with( - &shim(py, "validate_event_exporter_signature")?, + &shim(py, UserCodeHost::ValidateEventExporterSignature)?, &[ ("function", function.clone()), ("exporter_name", read.getattr("name")?), @@ -258,7 +260,7 @@ pub(crate) fn discover_event_exporter_declarations<'py>( seen.push((name, file_path.clone())); declarations.push(discovered( py, - "DiscoveredEventExporterDeclaration", + RecordId::DiscoveredEventExporterDeclaration, &[ ("file_path", file_path.clone()), ("relative_path", relative_path), @@ -286,7 +288,8 @@ fn exporter_provider_usages<'py>( .str()? .extract()?; let empty: Bound<'py, PyAny> = imported(py, "inspect", "Parameter")?.getattr("empty")?; - let annotations: Bound<'py, PyAny> = shim(py, "parameter_annotations")?.call1((&function,))?; + let annotations: Bound<'py, PyAny> = + shim(py, UserCodeHost::ParameterAnnotations)?.call1((&function,))?; for (index, pair) in annotations.try_iter()?.enumerate() { if index == 0 { continue; @@ -344,7 +347,7 @@ pub(crate) fn bind_event_exporter_declarations<'py>( pairs.push((field, declaration.getattr(field)?)); } pairs.push(("provider_usages", usages)); - bound.push(discovered(py, "DiscoveredEventExporter", &pairs)?); + bound.push(discovered(py, RecordId::DiscoveredEventExporter, &pairs)?); } tuple_of(py, bound) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/imported_python_nodes.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/imported_python_nodes.rs index b2fb22082c..2aacc4c22e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/imported_python_nodes.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/imported_python_nodes.rs @@ -6,35 +6,29 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyRes use pyo3::types::{PyBytes, PyDict, PyDictMethods, PyList, PyString, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::discovery::imported_modules::{ - MODELS_MODULE, NATIVE_MODULE, ProjectDir, definition, discovered, discovery_error, - discovery_error_from, is_exception, module_functions, provider_by_name, provider_usages, shim, - tuple_of, + ProjectDir, definition, discovered, discovery_error, discovery_error_from, is_exception, + module_functions, provider_by_name, provider_usages, shim, tuple_of, }; use crate::bindings::_helpers::discovery::imported_providers; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use sqlbuild_core::constants::PYTHON_NODE_ROOT; -const NODE_ERROR: &str = "PythonNodeDiscoveryError"; -const AUDIT_CASE: (&str, &str) = ("sqlbuild.python_nodes.models", "AuditCase"); -const READ_FACTORY: (&str, &str) = ( - "sqlbuild.python_nodes.main.read_factory_definition", - "read_factory_definition", -); -const READ_AUDIT_FACTORY: (&str, &str) = ( - "sqlbuild.python_nodes.main.read_audit_factory_definition", - "read_audit_factory_definition", -); -const READ_HOOK: (&str, &str) = ( - "sqlbuild.python_nodes.main.read_hook_definition", - "read_hook_definition", -); +const NODE_ERROR: ErrorId = ErrorId::PythonNodeDiscovery; +const AUDIT_CASE: UserCodeHost = UserCodeHost::AuditCase; +const READ_FACTORY: UserCodeHost = UserCodeHost::ReadFactoryDefinition; +const READ_AUDIT_FACTORY: UserCodeHost = UserCodeHost::ReadAuditFactoryDefinition; +const READ_HOOK: UserCodeHost = UserCodeHost::ReadHookDefinition; const NOT_A_NODE: &str = "that is not a SQLBuild task, asset, loader, or check"; /// One Python-node kind: its reader, record class, bucket and copied definition fields. struct NodeKind { - reader: (&'static str, &'static str), - record: &'static str, + reader: UserCodeHost, + record: RecordId, bucket: usize, fields: &'static [&'static str], } @@ -42,11 +36,8 @@ struct NodeKind { /// Readers in precedence order; the first matching definition decides the kind. const NODE_KINDS: [NodeKind; 4] = [ NodeKind { - reader: ( - "sqlbuild.python_nodes.main.read_loader_definition", - "read_loader_definition", - ), - record: "DiscoveredLoaderFunction", + reader: UserCodeHost::ReadLoaderDefinition, + record: RecordId::DiscoveredLoaderFunction, bucket: 0, fields: &[ "depends_on", @@ -60,11 +51,8 @@ const NODE_KINDS: [NodeKind; 4] = [ ], }, NodeKind { - reader: ( - "sqlbuild.python_nodes.main.read_task_definition", - "read_task_definition", - ), - record: "DiscoveredTaskFunction", + reader: UserCodeHost::ReadTaskDefinition, + record: RecordId::DiscoveredTaskFunction, bucket: 1, fields: &[ "depends_on", @@ -76,11 +64,8 @@ const NODE_KINDS: [NodeKind; 4] = [ ], }, NodeKind { - reader: ( - "sqlbuild.python_nodes.main.read_asset_definition", - "read_asset_definition", - ), - record: "DiscoveredAssetFunction", + reader: UserCodeHost::ReadAssetDefinition, + record: RecordId::DiscoveredAssetFunction, bucket: 2, fields: &[ "depends_on", @@ -94,11 +79,8 @@ const NODE_KINDS: [NodeKind; 4] = [ ], }, NodeKind { - reader: ( - "sqlbuild.python_nodes.main.read_check_definition", - "read_check_definition", - ), - record: "DiscoveredCheckFunction", + reader: UserCodeHost::ReadCheckDefinition, + record: RecordId::DiscoveredCheckFunction, bucket: 3, fields: &[ "depends_on", @@ -191,10 +173,11 @@ impl<'py> Buckets<'py> { )); } let cases: Vec> = audit_cases(&function, (&name, &relative_text))?; - let line: Bound<'py, PyAny> = shim(py, "source_line")?.call1((&function,))?; + let line: Bound<'py, PyAny> = + shim(py, UserCodeHost::SourceLine)?.call1((&function,))?; let record: Bound<'py, PyAny> = discovered( py, - "DiscoveredAuditFactory", + RecordId::DiscoveredAuditFactory, &[ ("name", read.getattr("name")?), ("function", function.clone()), @@ -259,7 +242,7 @@ impl<'py> Buckets<'py> { let mut modules: Vec> = Vec::new(); for file_path in project.module_files(PYTHON_NODE_ROOT, false)? { let module: Bound<'py, PyAny> = - project.load("import_python_root_module", &file_path)?; + project.load(UserCodeHost::ImportPythonRootModule, &file_path)?; modules.push(ImportedModule { file_path, module }); } let mut direct: HashMap> = HashMap::new(); @@ -293,7 +276,7 @@ fn node_definition_names(function: &Bound<'_, PyAny>) -> PyResult) -> PyResult { - shim(function.py(), "requires_arguments")? + shim(function.py(), UserCodeHost::RequiresArguments)? .call1((function,))? .is_truthy() } @@ -338,7 +321,7 @@ fn audit_cases<'py>( ), )); } - let audit_case: Bound<'py, PyAny> = imported(py, AUDIT_CASE.0, AUDIT_CASE.1)?; + let audit_case: Bound<'py, PyAny> = host::entry(py, AUDIT_CASE)?; let mut cases: Vec> = Vec::new(); for (index, item) in result.try_iter()?.enumerate() { let item: Bound<'py, PyAny> = item?; @@ -416,7 +399,7 @@ pub(crate) fn discover_python_node_functions<'py>( let node_root: Bound<'py, PyAny> = project.child(PYTHON_NODE_ROOT)?; if node_root.call_method0("is_dir")?.is_truthy()? { let saved: Bound<'py, PyAny> = call_with( - &shim(py, "install_python_root_package")?, + &shim(py, UserCodeHost::InstallPythonRootPackage)?, &[ ("node_root", node_root), ("project_dir", project.path.clone()), @@ -424,7 +407,7 @@ pub(crate) fn discover_python_node_functions<'py>( )?; let discovered_nodes: PyResult<()> = buckets.discover_nodes(&project, &providers); call_with( - &shim(py, "restore_python_root_modules")?, + &shim(py, UserCodeHost::RestorePythonRootModules)?, &[("saved_modules", saved)], )?; discovered_nodes?; @@ -432,7 +415,7 @@ pub(crate) fn discover_python_node_functions<'py>( let [loaders, tasks, assets, checks] = buckets.nodes; discovered( py, - "DiscoveredPythonNodeFunctions", + RecordId::DiscoveredPythonNodeFunctions, &[ ("loaders", tuple_of(py, loaders)?), ("tasks", tuple_of(py, tasks)?), @@ -467,7 +450,7 @@ pub(crate) fn discover_hook_functions<'py>( None => PyDict::new(py), }; let hook_files: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "discover_python_hook_files")?, + ®istry::native(py, NativeAttr::DiscoverPythonHookFiles)?, &[("project_dir", project.path.clone())], )?; let mut hooks: Vec> = Vec::new(); @@ -475,7 +458,8 @@ pub(crate) fn discover_hook_functions<'py>( for hook_file in hook_files.try_iter()? { let hook_file: Bound<'py, PyDict> = hook_file?.cast_into()?; let file_path: Bound<'py, PyAny> = hook_file.as_any().get_item("file_path")?; - let module: Bound<'py, PyAny> = project.load("load_python_node_module", &file_path)?; + let module: Bound<'py, PyAny> = + project.load(UserCodeHost::LoadPythonNodeModule, &file_path)?; for function in module_functions(&module)? { let Some(read) = definition(&function, READ_HOOK)? else { continue; @@ -504,7 +488,7 @@ pub(crate) fn discover_hook_functions<'py>( arguments.set_item(field, value)?; } hooks.push( - imported(py, MODELS_MODULE, "DiscoveredHookFunction")? + registry::class(py, RecordId::DiscoveredHookFunction)? .call((), Some(&arguments))?, ); } @@ -535,7 +519,7 @@ pub(crate) fn discover_materialization_files<'py>( let mut files: Vec> = Vec::new(); for file_path in project.module_files("materializations", false)? { let materialize: Bound<'py, PyAny> = - project.load("load_materialize_function", &file_path)?; + project.load(UserCodeHost::LoadMaterializeFunction, &file_path)?; let usages: Bound<'py, PyAny> = if materialize.is_none() { tuple_of(py, Vec::new())? } else { @@ -543,7 +527,7 @@ pub(crate) fn discover_materialization_files<'py>( }; files.push(discovered( py, - "DiscoveredMaterializationFile", + RecordId::DiscoveredMaterializationFile, &[ ("file_path", file_path.clone()), ("relative_path", project.relative(&file_path)?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/model_file_record.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/model_file_record.rs index f948566f07..a95fe3ec57 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/model_file_record.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/model_file_record.rs @@ -1,5 +1,7 @@ //! A discovered SQL model file as a native record whose Python attributes are built on demand. +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::exceptions::PyTypeError; use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::sync::PyOnceLock; @@ -7,8 +9,6 @@ use pyo3::types::{PyDict, PyDictMethods, PyString, PyTuple}; use pyo3::{FromPyObject, IntoPyObjectExt, pyclass, pymethods}; use sqlbuild_discovery::models::LineColumnSpan; -const SOURCE_LOCATION_MODULE: &str = "sqlbuild._native"; -const SOURCE_LOCATION_CLASS: &str = "SourceLocation"; const FIELD_NAMES: [&str; 10] = [ "file_path", "relative_path", @@ -62,9 +62,7 @@ impl Locations { Ok(self .projected .get_or_try_init(py, || { - let class: Bound<'py, PyAny> = py - .import(SOURCE_LOCATION_MODULE)? - .getattr(SOURCE_LOCATION_CLASS)?; + let class: Bound<'py, PyAny> = registry::class(py, RecordId::SourceLocation)?; let dict: Bound<'py, PyDict> = PyDict::new(py); for (name, span) in &self.rows { let location: Bound<'py, PyAny> = class.call1(( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/python_function_files.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/python_function_files.rs index 52a298c6c9..261782f5b7 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/python_function_files.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/python_function_files.rs @@ -17,17 +17,15 @@ use sqlbuild_pysource::udf_functions::main::udf_function::udf_function; use sqlbuild_pysource::udf_functions::models::{UdfFunction, UdfShape}; use crate::bindings::_helpers::boundary::panics::compiler_error; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::discovery::declaration_records::path_class; use crate::bindings::_helpers::discovery::project_files::path_object; use crate::bindings::_helpers::discovery::raised::{failure_error, read_error, stage_error}; use crate::bindings::_helpers::discovery::snapshot::{in_snapshot, project_root, project_tree}; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::models::NativeProjectTree; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const DISCOVERY_ERRORS_MODULE: &str = "sqlbuild._native"; -const PARSE_ERROR_CLASS: &str = "ModelSqlParseError"; const AST_MODULE: &str = "ast"; const DESCRIPTION_KEY: &str = "description"; const ENTRY_POINT_KEY: &str = "entry_point"; @@ -45,7 +43,7 @@ fn parse_error(source: &FunctionSource<'_>, detail: &str, cause: Option) let py: Python<'_> = source.file_path.py(); let raised = (|| { let message: String = format!("Python function '{}' {detail}", source.file_path.str()?); - imported(py, DISCOVERY_ERRORS_MODULE, PARSE_ERROR_CLASS)?.call1((message,)) + registry::error(py, ErrorId::ModelSqlParse)?.call1((message,)) })(); match raised { Ok(instance) => { @@ -128,8 +126,8 @@ fn unsupported_keys(source: &FunctionSource<'_>, function: &UdfFunction) -> PyRe let help: String = unsupported_keys_help(&keys, &supported); let keywords: Bound<'_, PyDict> = PyDict::new(py); keywords.set_item("help", help)?; - let error: Bound<'_, PyAny> = imported(py, DISCOVERY_ERRORS_MODULE, PARSE_ERROR_CLASS)? - .call((message,), Some(&keywords))?; + let error: Bound<'_, PyAny> = + registry::error(py, ErrorId::ModelSqlParse)?.call((message,), Some(&keywords))?; Err(PyErr::from_value(error)) } @@ -250,7 +248,7 @@ fn function_record<'py>( }; let (header_values, entry_point, body_python) = parsed_function(&source)?; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DiscoveredPythonFunctionFile")?, + ®istry::class(py, RecordId::DiscoveredPythonFunctionFile)?, &[ ("file_path", source.file_path.clone()), ("relative_path", source.relative_path.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/raised.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/raised.rs index 4dda7e1e1d..f126bcd051 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/raised.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/raised.rs @@ -10,48 +10,28 @@ use sqlbuild_discovery::models::{DiscoveryFailure, ReadFailure, StageFailure}; use sqlbuild_discovery::tree::models::ProjectTree; use crate::bindings::_helpers::discovery::project_files::path_object; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; -const DISCOVERY_ERRORS_MODULE: &str = "sqlbuild._native"; -const IDENTITY_ERRORS_MODULE: &str = "sqlbuild._native"; const BUILTINS_MODULE: &str = "builtins"; const OS_MODULE: &str = "os"; const DECODE_ENCODING: &str = "utf-8"; /// The exception class of each native failure kind. -const FAILURE_CLASSES: [(&str, &str, &str); 13] = [ - ("model_sql", DISCOVERY_ERRORS_MODULE, "ModelSqlParseError"), - ( - "declaration", - DISCOVERY_ERRORS_MODULE, - "DeclarationParseError", - ), - ("sql_test", DISCOVERY_ERRORS_MODULE, "SqlTestParseError"), - ( - "sql_scenario", - DISCOVERY_ERRORS_MODULE, - "SqlScenarioParseError", - ), - ("schema", DISCOVERY_ERRORS_MODULE, "SchemaParseError"), - ("source", DISCOVERY_ERRORS_MODULE, "SourceParseError"), - ("project_path", DISCOVERY_ERRORS_MODULE, "ProjectPathError"), - ("sql_hook", DISCOVERY_ERRORS_MODULE, "SqlHookParseError"), - ("sql_audit", DISCOVERY_ERRORS_MODULE, "SqlAuditParseError"), - ( - "resource_identity", - IDENTITY_ERRORS_MODULE, - "ResourceIdentityError", - ), - ( - "conflict", - DISCOVERY_ERRORS_MODULE, - "DiscoveryConflictError", - ), - ("seed", DISCOVERY_ERRORS_MODULE, "SeedDiscoveryError"), - ( - "python_path", - DISCOVERY_ERRORS_MODULE, - "ProjectPythonPathError", - ), +const FAILURE_CLASSES: [(&str, ErrorId); 13] = [ + ("model_sql", ErrorId::ModelSqlParse), + ("declaration", ErrorId::DeclarationParse), + ("sql_test", ErrorId::SqlTestParse), + ("sql_scenario", ErrorId::SqlScenarioParse), + ("schema", ErrorId::SchemaParse), + ("source", ErrorId::SourceParse), + ("project_path", ErrorId::ProjectPath), + ("sql_hook", ErrorId::SqlHookParse), + ("sql_audit", ErrorId::SqlAuditParse), + ("resource_identity", ErrorId::ResourceIdentity), + ("conflict", ErrorId::DiscoveryConflict), + ("seed", ErrorId::SeedDiscovery), + ("python_path", ErrorId::ProjectPythonPath), ]; fn raised(result: PyResult>) -> PyErr { @@ -68,15 +48,13 @@ pub(crate) fn discovery_error( message: String, help: Option, ) -> PyErr { - let Some((_, module, class)) = FAILURE_CLASSES.iter().find(|(name, _, _)| *name == kind) else { + let Some((_, class)) = FAILURE_CLASSES.iter().find(|(name, _)| *name == kind) else { return PyKeyError::new_err(kind.to_owned()); }; raised((|| { let keywords: Bound<'_, PyDict> = PyDict::new(py); keywords.set_item("help", help)?; - py.import(*module)? - .getattr(*class)? - .call((message,), Some(&keywords)) + registry::error(py, *class)?.call((message,), Some(&keywords)) })()) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/scope_snapshot.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/scope_snapshot.rs index cf869ed15b..d18a1db2d6 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/scope_snapshot.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/scope_snapshot.rs @@ -7,7 +7,7 @@ use pyo3::prelude::{ use pyo3::types::{PyDict, PyDictMethods, PyList, PyListMethods, PyString, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::contracts::project_config::{ load_local_config_file, load_project_config_file, }; @@ -24,10 +24,8 @@ use crate::bindings::_helpers::discovery::sql_test_records::{ }; use crate::bindings::_helpers::discovery::yaml_records::discover_source_files; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const MODELS_MODULE: &str = "sqlbuild._native"; -const CONTRACT_MODELS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use sqlbuild_core::constants::PROJECT_CONFIG_FILENAME; const LOCAL_CONFIG_FILENAME: &str = "sqlbuild_local.toml"; const FALLBACK_PROJECT_NAME: &str = "project"; @@ -68,7 +66,7 @@ impl<'py> Faults<'py> { None => py.None().into_bound(py), }; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DiscoveryFileFault")?, + ®istry::class(py, RecordId::DiscoveryFileFault)?, &[ ("path", path), ("message", PyString::new(py, &message).into_any()), @@ -122,7 +120,7 @@ fn configs<'py>( PyString::new(py, FALLBACK_PROJECT_NAME).into_any() }; call_with( - &imported(py, CONTRACT_MODELS_MODULE, "ProjectConfig")?, + ®istry::class(py, RecordId::ProjectConfig)?, &[ ("name", name), ("adapter", PyString::new(py, FALLBACK_ADAPTER).into_any()), @@ -134,7 +132,7 @@ fn configs<'py>( faults, (&list, Some(LOCAL_CONFIG_FILENAME)), load_local_config_file(py, project_dir).map(|config| config.into_bound(py)), - || imported(py, CONTRACT_MODELS_MODULE, "LocalConfig")?.call0(), + || registry::class(py, RecordId::LocalConfig)?.call0(), )?; Ok((project_config, local_config, list)) } @@ -229,9 +227,9 @@ fn tolerant_discovery<'py>(project_dir: &Bound<'py, PyAny>) -> PyResult = - imported(py, MODELS_MODULE, "DiscoveredProjectInputs")?.call((), Some(&inputs))?; + registry::class(py, RecordId::DiscoveredProjectInputs)?.call((), Some(&inputs))?; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "TolerantScopeDiscovery")?, + ®istry::class(py, RecordId::TolerantScopeDiscovery)?, &[ ("discovered_inputs", discovered_inputs), ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/sql_test_blocks.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/sql_test_blocks.rs index 2913333a00..21d9b44d28 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/sql_test_blocks.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/sql_test_blocks.rs @@ -1,6 +1,5 @@ //! SQL `TEST(...)` blocks validated natively and built as discovery records. -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class; use pyo3::IntoPyObjectExt; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; @@ -10,21 +9,16 @@ use sqlbuild_model_config::sql_tests::models::{SqlTestBlock, SqlTestCase}; use sqlbuild_model_config::sql_values::models::{RawKey, RawSqlValue}; use crate::bindings::_helpers::contracts::sql_values::{SqlValueClasses, raw_value, sql_value}; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const MODELS_MODULE: &str = "sqlbuild._native"; -const ERRORS_MODULE: &str = "sqlbuild._native"; const TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; -const SQL_VALUE_TYPES_MODULE: &str = "sqlbuild._native"; pub(crate) type NativeBlock<'py> = (Bound<'py, PyDict>, String, Bound<'py, PyAny>); fn parse_error(py: Python<'_>, message: String) -> PyResult { Ok(PyErr::from_value( - py.import(ERRORS_MODULE)? - .getattr("SqlTestParseError")? - .call1((message,))?, + registry::error(py, ErrorId::SqlTestParse)?.call1((message,))?, )) } @@ -50,9 +44,11 @@ fn case_record<'py>( [name.into_bound_py_any(py)?, sql_value(value, classes)?], )?); } - py.import(NATIVE_RECORDS_MODULE)? - .getattr("DiscoveredSqlTestCase")? - .call1((case.name.as_str(), PyTuple::new(py, values)?, case_index)) + registry::class(py, RecordId::DiscoveredSqlTestCase)?.call1(( + case.name.as_str(), + PyTuple::new(py, values)?, + case_index, + )) } fn block_record<'py>( @@ -61,13 +57,12 @@ fn block_record<'py>( block: SqlTestBlock, classes: &SqlValueClasses<'py>, ) -> PyResult> { - let models = py.import(MODELS_MODULE)?; - let kind = py.import(SQL_VALUE_TYPES_MODULE)?.getattr("SqlValueKind")?; + let kind = registry::class(py, RecordId::SqlValueKind)?; let parameters = block .parameters .iter() .map(|parameter| { - models.getattr("SqlTestParameterDeclaration")?.call1(( + registry::class(py, RecordId::SqlTestParameterDeclaration)?.call1(( parameter.name.as_str(), kind.call1((parameter.value_type,))?, parameter.nullable, @@ -96,7 +91,7 @@ fn block_record<'py>( keywords.set_item("cursor_start", block.cursor_start)?; keywords.set_item("cursor_end", block.cursor_end)?; keywords.set_item("sql_body_span", &native.2)?; - shape_class(models.as_any(), "DiscoveredSqlTestBlock")?.call((), Some(&keywords)) + registry::class(py, RecordId::DiscoveredSqlTestBlock)?.call((), Some(&keywords)) } /// Each `(header, sql_body, span)` block of a SQL test file as a `DiscoveredSqlTestBlock`. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/sql_test_records.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/sql_test_records.rs index 624322f883..3e0f807641 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/sql_test_records.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/sql_test_records.rs @@ -27,10 +27,11 @@ use crate::bindings::_helpers::discovery::sorted_keys::sorted_keys; use crate::bindings::_helpers::discovery::sql_test_blocks::{ sql_test_block_records, validate_block_names, }; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sqltext::authored_values::map_to_python; use crate::bindings::models::NativeProjectTree; -const MODELS_MODULE: &str = "sqlbuild._native"; const SQL_TESTS_OWNERSHIP_ROOT: &str = "tests/unit"; const SQL_SCENARIOS_OWNERSHIP_ROOT: &str = "tests/scenarios"; const PATHLIB_MODULE: &str = "pathlib"; @@ -127,7 +128,7 @@ fn test_file<'py>( let (file_path, relative_path) = paths; let py: Python<'py> = file_path.py(); call_with( - &imported(py, MODELS_MODULE, "DiscoveredSqlTestFile")?, + ®istry::class(py, RecordId::DiscoveredSqlTestFile)?, &[ ("file_path", file_path.clone()), ("relative_path", relative_path.clone()), @@ -180,7 +181,7 @@ fn scenario_file<'py>( )); } call_with( - &imported(py, MODELS_MODULE, "DiscoveredSqlScenarioFile")?, + ®istry::class(py, RecordId::DiscoveredSqlScenarioFile)?, &[ ("file_path", file_path.clone()), ("relative_path", relative_path.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/yaml_entries.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/yaml_entries.rs index 1f360f8c84..66b0625650 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/yaml_entries.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/yaml_entries.rs @@ -16,14 +16,13 @@ use sqlbuild_model_config::errors::{ConfigError, ErrorClass}; use crate::bindings::_helpers::model_config::authored_nodes::PyNode; use crate::bindings::_helpers::model_config::contract_classes::yaml_entry_classes; use crate::bindings::_helpers::model_config::header_metadata::{ContractClasses, audit_tuple}; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; -const DISCOVERY_ERRORS_MODULE: &str = "sqlbuild._native"; -const IDENTITY_ERRORS_MODULE: &str = "sqlbuild._native"; -const IDENTITY_ERROR: &str = "ResourceIdentityError"; /// The directory whose schema YAML files may declare seeds. const SEEDS_DIRECTORY: &str = "seeds"; -const SCHEMA_PARSE_ERROR: &str = "SchemaParseError"; -const SOURCE_PARSE_ERROR: &str = "SourceParseError"; +const SCHEMA_PARSE_ERROR: ErrorId = ErrorId::SchemaParse; +const SOURCE_PARSE_ERROR: ErrorId = ErrorId::SourceParse; /// The Python classes parsed YAML entries become. #[derive(FromPyObject)] @@ -46,12 +45,10 @@ pub(crate) struct YamlClasses<'py> { } /// The Python error `error` raises: a resource identity error, else the file kind's parse error. -fn parse_error(py: Python<'_>, error: ConfigError, parse_class: &str) -> PyResult { +fn parse_error(py: Python<'_>, error: ConfigError, parse_class: ErrorId) -> PyResult { let class: Bound<'_, PyAny> = match error.class { - ErrorClass::ResourceIdentity => { - py.import(IDENTITY_ERRORS_MODULE)?.getattr(IDENTITY_ERROR)? - } - _ => py.import(DISCOVERY_ERRORS_MODULE)?.getattr(parse_class)?, + ErrorClass::ResourceIdentity => registry::error(py, ErrorId::ResourceIdentity)?, + _ => registry::error(py, parse_class)?, }; let keywords: Bound<'_, PyDict> = PyDict::new(py); if let Some(help) = error.help { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/yaml_records.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/yaml_records.rs index 2f34461ba9..a70c58da43 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/yaml_records.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/yaml_records.rs @@ -29,11 +29,10 @@ use crate::bindings::_helpers::discovery::snapshot::{ use crate::bindings::_helpers::discovery::yaml_entries::{ parse_schema_document, parse_sources_document, }; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::models::{NativeProjectTree, YamlDiscoveryRequest}; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const MODELS_MODULE: &str = "sqlbuild._native"; /// The most YAML bytes one native load reads before its files are built and released. const YAML_BATCH_BYTES: u64 = 4 * 1024 * 1024; const PATHLIB_MODULE: &str = "pathlib"; @@ -224,7 +223,7 @@ pub(crate) fn discover_source_files<'py>( project_dir: project_dir.clone(), on_fault, }; - let class: Bound<'py, PyAny> = imported(py, MODELS_MODULE, "DiscoveredSourceFile")?; + let class: Bound<'py, PyAny> = registry::class(py, RecordId::DiscoveredSourceFile)?; let mut records: Vec> = Vec::new(); load_files( project_dir, @@ -284,7 +283,7 @@ pub(crate) fn discover_schema_files<'py>( if paths.is_empty() { return record_tuple(py, Vec::new()); } - let class: Bound<'py, PyAny> = imported(py, NATIVE_RECORDS_MODULE, "DiscoveredSchemaFile")?; + let class: Bound<'py, PyAny> = registry::class(py, RecordId::DiscoveredSchemaFile)?; let mut records: Vec> = Vec::new(); load_files( project_dir, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/constants.rs index cd153535c8..7e9756e372 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/constants.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/constants.rs @@ -1,13 +1,5 @@ //! Python modules, thread names and wait limits of native lifecycle-event export. -pub(crate) const EXCEPTIONS_MODULE: &str = "sqlbuild.runtime.event_exporting.exceptions"; -pub(crate) const SEVERITY_MODULE: &str = "sqlbuild._native"; -pub(crate) const INVOKE_LIFECYCLE_MODULE: &str = - "sqlbuild.runtime.event_exporting.main._invoke_lifecycle_event_sink"; -pub(crate) const RUN_OUTPUT_SINKS_MODULE: &str = - "sqlbuild.runtime.event_exporting.main._run_command_output_sinks"; -pub(crate) const SESSION_MODULE: &str = "sqlbuild.provider.main.session"; -pub(crate) const OUTPUT_MODELS_MODULE: &str = "sqlbuild.runtime.output_capture.models"; pub(crate) const SCOPE_CONTEXT_VARIABLE: &str = "sqlbuild_event_exporter_command_scope"; pub(crate) const DISPATCHER_THREAD_NAME: &str = "sqlbuild-event-exporter-dispatcher"; pub(crate) const NOTIFIER_THREAD_NAME: &str = "sqlbuild-event-exporter-notifier"; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/dispatcher.rs b/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/dispatcher.rs index 676c3bccf0..1158baef24 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/dispatcher.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/dispatcher.rs @@ -29,8 +29,7 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ }; use crate::bindings::_helpers::event_exporting::constants::{ DISPATCHER_THREAD_NAME, EXIT_JOIN_SECONDS, HEALTH_THREAD_NAME, JOIN_GRACE_SECONDS, NO_SECONDS, - NOTIFIER_THREAD_NAME, OUTPUT_SINK_THREAD_NAME, POLL_SECONDS, RUN_OUTPUT_SINKS_MODULE, - WORKER_ATTRIBUTE, + NOTIFIER_THREAD_NAME, OUTPUT_SINK_THREAD_NAME, POLL_SECONDS, WORKER_ATTRIBUTE, }; use crate::bindings::_helpers::event_exporting::models::{ BoundEventExporter, EventExportSummary, EventExporterFailure, @@ -39,6 +38,8 @@ use crate::bindings::_helpers::event_exporting::values::{ export_policy, input_error, severity_from, state_error, }; use crate::bindings::_helpers::event_exporting::workers::{DispatcherWorker, WorkerRole}; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; use crate::bindings::_helpers::native_threads::constants::OUTPUT_SUPPRESSED_ATTRIBUTE; use crate::bindings::_helpers::native_threads::exit_signal::{ interpreter_exiting, signal_interpreter_exit, @@ -510,7 +511,7 @@ impl Shared { &[ ( "target", - imported(py, RUN_OUTPUT_SINKS_MODULE, "run_command_output_sinks")?, + host::entry(py, UserCodeHost::RunCommandOutputSinks)?, ), ("kwargs", arguments.into_any()), ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/scope.rs b/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/scope.rs index fbfb35564c..adcf228f24 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/scope.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/scope.rs @@ -11,12 +11,13 @@ use pyo3::sync::PyOnceLock; use pyo3::types::{PyBool, PyDict, PyDictMethods, PyTuple}; use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, context_variable, imported, -}; -use crate::bindings::_helpers::event_exporting::constants::{ - OUTPUT_MODELS_MODULE, SCOPE_CONTEXT_VARIABLE, SESSION_MODULE, + KeywordArguments, call_with, context_variable, }; +use crate::bindings::_helpers::event_exporting::constants::SCOPE_CONTEXT_VARIABLE; use crate::bindings::_helpers::event_exporting::models::BoundEventExporter; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; static CURRENT_SCOPE: PyOnceLock> = PyOnceLock::new(); @@ -103,8 +104,7 @@ fn bindings<'py>( .into_any(), ); } - let sink_class: Bound<'py, PyAny> = - imported(py, OUTPUT_MODELS_MODULE, "BoundCommandOutputSink")?; + let sink_class: Bound<'py, PyAny> = host::entry(py, ExecutorHost::BoundCommandOutputSink)?; let mut sinks: Vec> = Vec::new(); for sink in command_output_sinks.try_iter()? { let sink: Bound<'py, PyAny> = sink?; @@ -162,7 +162,7 @@ impl EventExporterCommandScope { return self.bind_dispatcher(py, (empty.clone(), empty)); } let session: Bound<'_, PyAny> = call_with( - &imported(py, SESSION_MODULE, "build_provider_session")?, + &host::entry(py, UserCodeHost::BuildProviderSession)?, &[ ("discovered_providers", providers), ("allow_shared", PyBool::new(py, false).to_owned().into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/values.rs b/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/values.rs index c9a7c33915..8b99a62e64 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/values.rs @@ -12,8 +12,10 @@ use sqlbuild_core::event_export::models::{ExportPolicy, ExportSeverity, Exporter use sqlbuild_core::lifecycle_events::constants::CURRENT_LIFECYCLE_EVENT_SCHEMA_VERSION; use sqlbuild_core::lifecycle_events::main::catalog::catalog; -use crate::bindings::_helpers::compile_driver::python_calls::imported; -use crate::bindings::_helpers::event_exporting::constants::{EXCEPTIONS_MODULE, SEVERITY_MODULE}; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; /// `BoundEventExporter` parameters, in positional order. const BOUND_EXPORTER_PARAMETERS: [&str; 5] = [ @@ -28,8 +30,8 @@ const REQUIRED_BOUND_EXPORTER_PARAMETERS: usize = 3; static POLICIES: PyOnceLock> = PyOnceLock::new(); /// The exception class `name` of the event exporter exceptions module, raised with `message`. -pub(crate) fn exporter_error(py: Python<'_>, name: &str, message: &str) -> PyErr { - match imported(py, EXCEPTIONS_MODULE, name).and_then(|class| class.call1((message,))) { +pub(crate) fn exporter_error(py: Python<'_>, name: UserCodeHost, message: &str) -> PyErr { + match host::entry(py, name).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } @@ -37,12 +39,12 @@ pub(crate) fn exporter_error(py: Python<'_>, name: &str, message: &str) -> PyErr /// `EventExporterStateError(message)`. pub(crate) fn state_error(py: Python<'_>, message: &str) -> PyErr { - exporter_error(py, "EventExporterStateError", message) + exporter_error(py, UserCodeHost::EventExporterStateError, message) } /// `EventExporterInputError(message)`. pub(crate) fn input_error(py: Python<'_>, message: &str) -> PyErr { - exporter_error(py, "EventExporterInputError", message) + exporter_error(py, UserCodeHost::EventExporterInputError, message) } /// The Python `EventExportSeverity` member of `severity`. @@ -50,7 +52,7 @@ pub(crate) fn severity_object( py: Python<'_>, severity: ExportSeverity, ) -> PyResult> { - imported(py, SEVERITY_MODULE, "EventExportSeverity")?.call1((severity.value(),)) + registry::class(py, RecordId::EventExportSeverity)?.call1((severity.value(),)) } /// An `EventExportSeverity` or its string value, as `EventExportSeverity(value)` accepts it. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/workers.rs b/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/workers.rs index 2f4e6f1cce..1cde088682 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/workers.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/workers.rs @@ -13,13 +13,15 @@ use sqlbuild_core::event_export::models::{EventQueue, ExportSnapshot, QueuedEven use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; use crate::bindings::_helpers::event_exporting::constants::{ - INVOCATION_THREAD_PREFIX, INVOKE_LIFECYCLE_MODULE, NO_SECONDS, POLL_SECONDS, + INVOCATION_THREAD_PREFIX, NO_SECONDS, POLL_SECONDS, }; use crate::bindings::_helpers::event_exporting::dispatcher::{Exporter, Queued, Shared}; use crate::bindings::_helpers::event_exporting::models::{ EventExportSummary, EventExporterFailure, }; use crate::bindings::_helpers::event_exporting::values::error_type_of; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; use crate::bindings::_helpers::native_threads::exit_signal::interpreter_exiting; /// Which long-lived dispatcher thread a worker runs. @@ -199,7 +201,7 @@ impl DispatcherWorker { &[ ( "target", - imported(py, INVOKE_LIFECYCLE_MODULE, "invoke_lifecycle_event_sink")?, + host::entry(py, UserCodeHost::InvokeLifecycleEventSink)?, ), ("kwargs", arguments.into_any()), ("name", PyString::new(py, &name).into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/execution_history/conversion.rs b/crates/sqlbuild-python/src/bindings/_helpers/execution_history/conversion.rs index 5e51a40232..9dcb146d8e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/execution_history/conversion.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/execution_history/conversion.rs @@ -14,37 +14,30 @@ use sqlbuild_state::execution_history::types::UtcTime; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; use crate::bindings::_helpers::cursor::values::{DATETIME_MODULE, local_date_time}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; -pub(crate) const EXCEPTIONS_MODULE: &str = "sqlbuild.runtime.execution_history.exceptions"; -pub(crate) const MODELS_MODULE: &str = "sqlbuild.runtime.execution_history.models"; -const TYPES_MODULE: &str = "sqlbuild.runtime.execution_history.types"; -const EVENT_MODELS_MODULE: &str = "sqlbuild.runtime.observability.models"; -const EVENT_TO_JSON_MODULE: &str = "sqlbuild.runtime.observability.main.lifecycle_event_to_json"; -const EVENT_FROM_JSON_MODULE: &str = - "sqlbuild.runtime.observability.main.lifecycle_event_from_json"; const UTC_SUFFIX: &str = "Z"; const UTC_OFFSET: &str = "+00:00"; const DEFAULT_SCHEMA_VERSION: i64 = 1; const UTC_OFFSET_SECONDS: f64 = 0.0; -fn exception_name(kind: HistoryErrorKind) -> &'static str { +fn exception_class(kind: HistoryErrorKind) -> ExecutorHost { match kind { - HistoryErrorKind::Storage => "ExecutionHistoryStorageError", - HistoryErrorKind::IntegrityConflict => "IntegrityConflictError", - HistoryErrorKind::InvalidEvent => "InvalidEventError", - HistoryErrorKind::InvalidCursor => "InvalidCursorError", - HistoryErrorKind::InvalidFilter => "InvalidFilterError", - HistoryErrorKind::InvalidLimit => "InvalidLimitError", - HistoryErrorKind::UnsupportedSchemaVersion => "UnsupportedSchemaVersionError", - HistoryErrorKind::ProjectionConsistency => "ProjectionConsistencyError", + HistoryErrorKind::Storage => ExecutorHost::ExecutionHistoryStorageError, + HistoryErrorKind::IntegrityConflict => ExecutorHost::IntegrityConflictError, + HistoryErrorKind::InvalidEvent => ExecutorHost::InvalidEventError, + HistoryErrorKind::InvalidCursor => ExecutorHost::InvalidCursorError, + HistoryErrorKind::InvalidFilter => ExecutorHost::InvalidFilterError, + HistoryErrorKind::InvalidLimit => ExecutorHost::InvalidLimitError, + HistoryErrorKind::UnsupportedSchemaVersion => ExecutorHost::UnsupportedSchemaVersionError, + HistoryErrorKind::ProjectionConsistency => ExecutorHost::ProjectionConsistencyError, } } /// The execution history exception for a rejection of `kind`. pub(crate) fn rejection(py: Python<'_>, kind: HistoryErrorKind, message: &str) -> PyErr { - match imported(py, EXCEPTIONS_MODULE, exception_name(kind)) - .and_then(|class| class.call1((message,))) - { + match host::entry(py, exception_class(kind)).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } @@ -68,8 +61,12 @@ pub(crate) fn checked(py: Python<'_>, result: Result) -> PyR } /// Whether `error` is an instance of the execution history exception named `name`. -pub(crate) fn history_error_of_kind(py: Python<'_>, error: &PyErr, name: &str) -> PyResult { - Ok(error.is_instance(py, &imported(py, EXCEPTIONS_MODULE, name)?)) +pub(crate) fn history_error_of_kind( + py: Python<'_>, + error: &PyErr, + name: ExecutorHost, +) -> PyResult { + Ok(error.is_instance(py, &host::entry(py, name)?)) } /// `value` when it is an `int` other than `bool` that fits, as `isinstance(value, int)` checks. @@ -190,7 +187,7 @@ pub(crate) fn parsed_status(py: Python<'_>, text: &str) -> PyResult { if let Some(status) = known { return Ok(status); } - imported(py, TYPES_MODULE, "RunStatus")?.call1((text,))?; + host::entry(py, ExecutorHost::RunStatus)?.call1((text,))?; Ok(RunStatus::Unknown) } @@ -211,11 +208,7 @@ pub(crate) fn run_filter_spec(value: &Bound<'_, PyAny>) -> PyResult) -> PyResult { - event.is_instance(&imported( - event.py(), - EVENT_MODELS_MODULE, - "LifecycleEvent", - )?) + event.is_instance(&host::entry(event.py(), ExecutorHost::LifecycleEvent)?) } /// The `event_id` a known event carries or an opaque envelope holds as a string. @@ -232,19 +225,14 @@ fn raw_field<'py>(event: &Bound<'py, PyAny>, name: &str) -> PyResult) -> PyResult { - imported(event.py(), EVENT_TO_JSON_MODULE, "lifecycle_event_to_json")? + host::entry(event.py(), ExecutorHost::LifecycleEventToJson)? .call1((event,))? .extract() } /// `lifecycle_event_from_json(payload)`. pub(crate) fn event_from_json<'py>(payload: &Bound<'py, PyAny>) -> PyResult> { - imported( - payload.py(), - EVENT_FROM_JSON_MODULE, - "lifecycle_event_from_json", - )? - .call1((payload,)) + host::entry(payload.py(), ExecutorHost::LifecycleEventFromJson)?.call1((payload,)) } /// An opaque envelope's `occurred_at` when it is ISO text with a zero UTC offset. @@ -345,9 +333,9 @@ fn optional_python_time<'py>( /// The `RunRecord` of a projected run. pub(crate) fn run_record<'py>(py: Python<'py>, run: &RunRow) -> PyResult> { - let status = imported(py, TYPES_MODULE, "RunStatus")?.call1((run.status.value(),))?; + let status = host::entry(py, ExecutorHost::RunStatus)?.call1((run.status.value(),))?; call_with( - &imported(py, MODELS_MODULE, "RunRecord")?, + &host::entry(py, ExecutorHost::RunRecord)?, &[ ("run_id", run.run_id.clone().into_bound_py_any(py)?), ( @@ -413,7 +401,7 @@ pub(crate) fn stored_event<'py>( payload: &Bound<'py, PyAny>, ) -> PyResult> { call_with( - &imported(py, MODELS_MODULE, "StoredEvent")?, + &host::entry(py, ExecutorHost::StoredEvent)?, &[ ("storage_order", storage_order.into_bound_py_any(py)?), ("cursor", cursor.into_bound_py_any(py)?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/execution_history/postgres.rs b/crates/sqlbuild-python/src/bindings/_helpers/execution_history/postgres.rs index b55b674307..7482d7d61b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/execution_history/postgres.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/execution_history/postgres.rs @@ -42,13 +42,15 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ }; use crate::bindings::_helpers::execution_history::appends::prepared_batch; use crate::bindings::_helpers::execution_history::conversion::{ - MODELS_MODULE, checked, event_filter_spec, exact_int, history_error_of_kind, new_namespace, - now, optional_text, parsed_status, projection_facts, rejection, run_filter_spec, run_records, + checked, event_filter_spec, exact_int, history_error_of_kind, new_namespace, now, + optional_text, parsed_status, projection_facts, rejection, run_filter_spec, run_records, run_rows, storage_error, stored_event, text_of, utc, utc_time, }; use crate::bindings::_helpers::execution_history::statements::{ column, execute, execute_plain, execute_query, fetch_all, fetch_one, tuple_of, }; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; const PSYCOPG_MODULE: &str = "psycopg"; const PSYCOPG_ROWS_MODULE: &str = "psycopg.rows"; @@ -58,7 +60,7 @@ const CONNECT_FAILED: &str = "cannot connect to PostgreSQL execution history; ve const READ_FAILED: &str = "PostgreSQL execution history read failed"; const METADATA_INVALID: &str = "PostgreSQL execution history schema metadata is invalid"; const NAMESPACE_MISSING: &str = "PostgreSQL execution history cursor namespace is missing"; -const STORAGE_ERROR: &str = "ExecutionHistoryStorageError"; +const STORAGE_ERROR: ExecutorHost = ExecutorHost::ExecutionHistoryStorageError; /// The `psycopg.Error` class. fn driver_error_class(py: Python<'_>) -> PyResult> { @@ -667,7 +669,7 @@ impl PostgresExecutionHistory { None => py.None().into_bound(py), }; call_with( - &imported(py, MODELS_MODULE, "EventPage")?, + &host::entry(py, ExecutorHost::EventPage)?, &[ ("records", tuple_of(py, records)?.into_any()), ("next_cursor", next_cursor), @@ -734,7 +736,7 @@ impl PostgresExecutionHistory { None => py.None().into_bound(py), }; call_with( - &imported(py, MODELS_MODULE, "RunPage")?, + &host::entry(py, ExecutorHost::RunPage)?, &[ ("records", run_records(py, &runs)?.into_any()), ("next_cursor", next_cursor), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/execution_history/sqlite.rs b/crates/sqlbuild-python/src/bindings/_helpers/execution_history/sqlite.rs index 06767860f5..503d21f626 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/execution_history/sqlite.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/execution_history/sqlite.rs @@ -50,6 +50,8 @@ use crate::bindings::_helpers::execution_history::conversion::{ use crate::bindings::_helpers::execution_history::statements::{ column, execute, execute_plain, execute_query, fetch_one, locked, tuple_of, }; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; const SQLITE_MODULE: &str = "sqlite3"; const PATHLIB_MODULE: &str = "pathlib"; @@ -57,8 +59,8 @@ const THREADING_MODULE: &str = "threading"; const MILLIS_PER_SECOND: f64 = 1000.0; const CLOSED: &str = "SQLite execution history storage is closed"; const NOT_AWARE: &str = "SQLite history timestamp is not timezone-aware"; -const INTEGRITY_CONFLICT: &str = "IntegrityConflictError"; -const STORAGE_ERROR: &str = "ExecutionHistoryStorageError"; +const INTEGRITY_CONFLICT: ExecutorHost = ExecutorHost::IntegrityConflictError; +const STORAGE_ERROR: ExecutorHost = ExecutorHost::ExecutionHistoryStorageError; /// `Path(path).parent.mkdir(mode=0o700, parents=True, exist_ok=True)` for a file-backed history. fn prepare_history_directory(path: &str) -> std::io::Result<()> { @@ -634,11 +636,7 @@ impl SqliteExecutionHistory { None => py.None().into_bound(py), }; crate::bindings::_helpers::compile_driver::python_calls::call_with( - &imported( - py, - crate::bindings::_helpers::execution_history::conversion::MODELS_MODULE, - "EventPage", - )?, + &host::entry(py, ExecutorHost::EventPage)?, &[ ("records", tuple_of(py, records)?.into_any()), ("next_cursor", next_cursor), @@ -707,11 +705,7 @@ impl SqliteExecutionHistory { None => py.None().into_bound(py), }; crate::bindings::_helpers::compile_driver::python_calls::call_with( - &imported( - py, - crate::bindings::_helpers::execution_history::conversion::MODELS_MODULE, - "RunPage", - )?, + &host::entry(py, ExecutorHost::RunPage)?, &[ ("records", run_records(py, &runs)?.into_any()), ("next_cursor", next_cursor), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/exported_constants/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/exported_constants/constants.rs index 0718d4e3f7..c02be409fd 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/exported_constants/constants.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/exported_constants/constants.rs @@ -42,10 +42,9 @@ use crate::bindings::_helpers::compile_command::constants::{ REUSE_DISABLE_ENV_VAR, TRACKED_ENVIRONMENT_PREFIXES, UNTRACKED_ENVIRONMENT_NAMES, }; use crate::bindings::_helpers::frontier::stage_capture::CAPTURE_DIR_ENV_VAR; +use crate::bindings::_helpers::native_host::constants::CUSTOM_HOST_LAUNCH_MODULE; use crate::bindings::_helpers::result_output::event_writer::RESULT_PATH_ENV; -use crate::bindings::_helpers::rules::constants::{ - CUSTOM_HOST_LAUNCH_MODULE, CUSTOM_HOST_RUNTIME_VERSION, -}; +use crate::bindings::_helpers::rules::constants::CUSTOM_HOST_RUNTIME_VERSION; use crate::bindings::_helpers::semantic_shapes::shapes::{VALID_FROM_DEFAULT, VALID_TO_DEFAULT}; use crate::bindings::_helpers::state_rows::constants::MIGRATION_WRITE_ATTEMPTS; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/fingerprints/latest_rows.rs b/crates/sqlbuild-python/src/bindings/_helpers/fingerprints/latest_rows.rs index b764698f81..cc20c8e1c3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/fingerprints/latest_rows.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/fingerprints/latest_rows.rs @@ -2,6 +2,8 @@ use std::ffi::CString; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::exceptions::{PyUnicodeDecodeError, PyValueError}; use pyo3::prelude::{ Bound, PyAny, PyAnyMethods, PyErr, PyModule, PyModuleMethods, PyResult, Python, @@ -14,8 +16,6 @@ use sqlbuild_analysis::fingerprint_storage::main::latest_index::latest_index; use sqlbuild_analysis::fingerprint_storage::main::selected_positions::selected_positions; use sqlbuild_analysis::fingerprint_storage::models::{FingerprintIdentity, LatestIndex}; -const MODELS_MODULE: &str = "sqlbuild._native"; -const EXCEPTIONS_MODULE: &str = "sqlbuild._native"; const DATETIME_MODULE: &str = "datetime"; const BINASCII_MODULE: &str = "binascii"; const BASE64_MODULE: &str = "base64"; @@ -23,11 +23,7 @@ const TIMESTAMP_COLUMN: usize = 11; /// `FingerprintInputError(message)`. pub(crate) fn fingerprint_input_error(py: Python<'_>, message: &str) -> PyErr { - match py - .import(EXCEPTIONS_MODULE) - .and_then(|module| module.getattr("FingerprintInputError")) - .and_then(|class| class.call1((message,))) - { + match registry::error(py, ErrorId::FingerprintInput).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } @@ -40,8 +36,7 @@ pub(crate) fn fingerprint_set_from_rows<'py>( qualified_name: &str, rows: &Bound<'py, PyAny>, ) -> PyResult> { - let models: Bound<'py, PyModule> = py.import(MODELS_MODULE)?; - let fingerprint_class: Bound<'py, PyAny> = models.getattr("Fingerprint")?; + let fingerprint_class: Bound<'py, PyAny> = registry::class(py, RecordId::Fingerprint)?; let from_iso: Bound<'py, PyAny> = py .import(DATETIME_MODULE)? .getattr("datetime")? @@ -55,7 +50,7 @@ pub(crate) fn fingerprint_set_from_rows<'py>( qualified_name, )?); } - indexed_set(&models, schema, &fingerprints) + indexed_set(py, schema, &fingerprints) } /// `select_latest_fingerprints`: an unfiltered latest read narrowed as a filtered read would be. @@ -67,10 +62,9 @@ fn select_latest_fingerprints<'py>( node_names: Vec, filtered_node_types: Vec, ) -> PyResult> { - let models: Bound<'py, PyModule> = py.import(MODELS_MODULE)?; let schema: String = fingerprint_set.getattr("schema")?.extract()?; if node_names.is_empty() && filtered_node_types.is_empty() { - return indexed_set(&models, &schema, &[]); + return indexed_set(py, &schema, &[]); } let by_identity: Bound<'py, PyAny> = fingerprint_set.getattr("fingerprints_by_identity")?; let latest: Vec> = if by_identity.is_truthy()? { @@ -89,15 +83,14 @@ fn select_latest_fingerprints<'py>( .into_iter() .map(|position| latest[position].clone()) .collect(); - indexed_set(&models, &schema, &selected) + indexed_set(py, &schema, &selected) } fn indexed_set<'py>( - models: &Bound<'py, PyModule>, + py: Python<'py>, schema: &str, fingerprints: &[Bound<'py, PyAny>], ) -> PyResult> { - let py: Python<'py> = models.py(); let identities: Vec<(String, String)> = fingerprints .iter() .map(identity_of) @@ -115,7 +108,7 @@ fn indexed_set<'py>( kwargs.set_item("schema", schema)?; kwargs.set_item("fingerprints", by_name)?; kwargs.set_item("fingerprints_by_identity", by_identity)?; - models.getattr("FingerprintSet")?.call((), Some(&kwargs)) + registry::class(py, RecordId::FingerprintSet)?.call((), Some(&kwargs)) } fn identity_of(fingerprint: &Bound<'_, PyAny>) -> PyResult<(String, String)> { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/fingerprints/query_hashes.rs b/crates/sqlbuild-python/src/bindings/_helpers/fingerprints/query_hashes.rs index 745865ab83..9532cbf23b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/fingerprints/query_hashes.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/fingerprints/query_hashes.rs @@ -9,19 +9,16 @@ use sqlbuild_analysis::sql_tokens::main::normalized_query_text::normalized_query use sqlbuild_core::panics::main::is_compiler_panic::is_compiler_panic; use crate::bindings::_helpers::boundary::panics::compiler_error; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; use crate::bindings::types::CompilerDetach; -const EXCEPTIONS_MODULE: &str = "sqlbuild._native"; /// `QueryFingerprintError(message)`, or the compiler error a native panic becomes. fn query_fingerprint_error(py: Python<'_>, message: String) -> PyErr { if is_compiler_panic(&message) { return compiler_error(message); } - match py - .import(EXCEPTIONS_MODULE) - .and_then(|module| module.getattr("QueryFingerprintError")) - .and_then(|class| class.call1((message,))) - { + match registry::error(py, ErrorId::QueryFingerprint).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/fingerprints/storage_io.rs b/crates/sqlbuild-python/src/bindings/_helpers/fingerprints/storage_io.rs index b4b8774651..01ea896109 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/fingerprints/storage_io.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/fingerprints/storage_io.rs @@ -20,9 +20,11 @@ use crate::bindings::_helpers::fingerprints::storage_sql::{ fingerprint_insert_batches, fingerprint_insert_sql, }; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use sqlbuild_core::constants::FINGERPRINT_TABLE_NAME; -const MODELS_MODULE: &str = "sqlbuild._native"; -const ADAPTER_TYPES_MODULE: &str = "sqlbuild.adapter.contract.types"; const TIME_MODULE: &str = "time"; const LOGGING_MODULE: &str = "logging"; const LOGGER_NAME: &str = "sqlbuild.fingerprints"; @@ -79,7 +81,7 @@ fn create_sql( render_framework_type: &Bound<'_, PyAny>, ) -> PyResult { let py: Python<'_> = render_framework_type.py(); - let framework: Bound<'_, PyAny> = py.import(ADAPTER_TYPES_MODULE)?.getattr("FrameworkType")?; + let framework: Bound<'_, PyAny> = host::entry(py, AdapterHost::FrameworkType)?; let string_type: String = render_framework_type .call1((framework.getattr("STRING")?,))? .extract()?; @@ -269,9 +271,7 @@ fn empty_set<'py>(py: Python<'py>, schema: &str) -> PyResult> let keywords: Bound<'py, PyDict> = PyDict::new(py); keywords.set_item("schema", schema)?; keywords.set_item("fingerprints", PyDict::new(py))?; - py.import(MODELS_MODULE)? - .getattr("FingerprintSet")? - .call((), Some(&keywords)) + registry::class(py, RecordId::FingerprintSet)?.call((), Some(&keywords)) } /// `FingerprintInputError` for an unreadable table, caused by the warehouse error. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/format_command/command.rs b/crates/sqlbuild-python/src/bindings/_helpers/format_command/command.rs index d72d384f86..18734a6846 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/format_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/format_command/command.rs @@ -26,8 +26,9 @@ use crate::bindings::_helpers::format_command::run::{FormatOutcome, FormatReques use crate::bindings::_helpers::format_command::selection::{LintInputs, resolve_lint_inputs}; use crate::bindings::_helpers::frontier::encoder::emit_error; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; const SQL_ANALYSIS_SETTING_KEY: &str = "sql_analysis"; const DEFAULT_DIALECT: &str = "generic"; const START_MESSAGE: &str = "Formatting SQL START"; @@ -115,7 +116,7 @@ fn resolve_inputs<'py>( } if fix { resolved.discovered = Some(call_with( - &imported(py, NATIVE_MODULE, "discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[("project_dir", project_dir.clone())], )?); } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/format_command/config.rs b/crates/sqlbuild-python/src/bindings/_helpers/format_command/config.rs index 851ffca206..a2b573c76b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/format_command/config.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/format_command/config.rs @@ -9,10 +9,9 @@ use pyo3::types::PyString; use sqlbuild_rules::lint_text::main::resolve_format_settings::resolve_format_settings; use sqlbuild_rules::lint_text::models::FormatSettings; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const LINT_MODELS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; /// Format settings and whether lint suppressions are allowed. pub(crate) struct FormatConfig { @@ -30,7 +29,7 @@ pub(crate) fn format_config( resolve_format_settings(Path::new(&directory)).map_err(PyValueError::new_err)?; let allow_suppressions: bool = if settings.has_project_config { call_with( - &imported(py, NATIVE_MODULE, "load_rules_config")?, + ®istry::native(py, NativeAttr::LoadRulesConfig)?, &[("project_dir", project_dir.call_method0("resolve")?)], )? .getattr("allow_exceptions")? @@ -52,7 +51,7 @@ pub(crate) fn lint_config<'py>( ) -> PyResult> { let settings: &FormatSettings = &config.settings; call_with( - &imported(py, LINT_MODELS_MODULE, "LintConfig")?, + ®istry::class(py, RecordId::LintConfig)?, &[ ( "line_width", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/format_command/run.rs b/crates/sqlbuild-python/src/bindings/_helpers/format_command/run.rs index 7c4995d502..e1df98565a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/format_command/run.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/format_command/run.rs @@ -7,16 +7,14 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyBool, PyDict, PyDictMethods, PyList, PyListMethods, PyTuple}; use sqlbuild_core::format_command::main::collect_lint_files::collect_lint_files; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::discovery::description_inputs::externally_described_models; use crate::bindings::_helpers::format_command::files::{ FormatChange, file_dictionary, overlaid, persist_changes, sorted_items, }; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const FIXES_MODULE: &str = "sqlbuild._native"; -const FIXTURE_AUTOFIX_MODULE: &str = "sqlbuild._native"; -const LINT_ERRORS_MODULE: &str = "sqlbuild._native"; const DESCRIPTION_PRESENT: &str = "description-present"; /// What one format run formats and how. @@ -42,8 +40,8 @@ pub(crate) struct FormatOutcome<'py> { pub(crate) final_contents: Bound<'py, PyDict>, } -fn native<'py>(py: Python<'py>, name: &str) -> PyResult> { - imported(py, NATIVE_MODULE, name) +fn native<'py>(py: Python<'py>, name: NativeAttr) -> PyResult> { + registry::native(py, name) } /// Fixture-null rewrites proven safe by the contracts, when fixture inputs are known. @@ -57,7 +55,7 @@ fn fixture_autofix<'py>( return Ok(PyDict::new(py)); }; Ok(call_with( - &imported(py, FIXTURE_AUTOFIX_MODULE, "format_redundant_fixture_nulls")?, + ®istry::native(py, NativeAttr::FormatRedundantFixtureNulls)?, &[ ("files", files.clone().into_any()), ("project_dir", request.project_dir.clone()), @@ -78,7 +76,7 @@ fn format_bodies<'py>( .into_iter() .map(|(path, contents)| Ok(PyTuple::new(py, [path, contents])?.into_any())) .collect::>()?; - let result: Bound<'py, PyAny> = native(py, "format_lint_sql_bodies")?.call1(( + let result: Bound<'py, PyAny> = native(py, NativeAttr::FormatLintSqlBodies)?.call1(( PyList::new(py, items)?, request.project_dir, request.config, @@ -103,12 +101,12 @@ fn apply_fixes<'py>( } let current: Bound<'py, PyDict> = overlaid(files, &updated)?; call_with( - &native(py, "prepare_native_header_cache")?, + &native(py, NativeAttr::PrepareNativeHeaderCache)?, &[("files", current.clone().into_any())], )?; for (path, contents) in sorted_items(¤t)? { let result: Bound<'py, PyAny> = call_with( - &native(py, "format_native_headers")?, + &native(py, NativeAttr::FormatNativeHeaders)?, &[ ("contents", contents.clone()), ("file_path", path.clone()), @@ -139,7 +137,7 @@ fn apply_fixes<'py>( updated.update(final_native.as_mapping())?; } let result: Bound<'py, PyAny> = call_with( - &native(py, "reject_unparseable_header_rewrites")?, + &native(py, NativeAttr::RejectUnparseableHeaderRewrites)?, &[ ("updated", updated.into_any()), ("config", request.config.clone()), @@ -163,11 +161,11 @@ fn lint_final_contents<'py>( for (path, contents) in sorted_items(files)? { let final_contents: Bound<'py, PyAny> = updated.get_item(&path)?.unwrap_or(contents); let headers: Bound<'py, PyAny> = call_with( - &native(py, "scan_headers")?, + &native(py, NativeAttr::ScanHeaders)?, &[("contents", final_contents.clone())], )?; let found: Bound<'py, PyAny> = call_with( - &native(py, "lint_native_headers")?, + &native(py, NativeAttr::LintNativeHeaders)?, &[ ("contents", final_contents), ("file_path", path), @@ -222,7 +220,7 @@ fn plan_rule_fixes<'py>( ) -> PyResult<(Bound<'py, PyDict>, Bound<'py, PyAny>, Bound<'py, PyAny>)> { let py: Python<'py> = files.py(); let result: Bound<'py, PyAny> = call_with( - &imported(py, FIXES_MODULE, "plan_rule_fixes")?, + ®istry::native(py, NativeAttr::PlanRuleFixes)?, &[ ("project_dir", request.project_dir.clone()), ("files", files.clone().into_any()), @@ -261,7 +259,7 @@ pub(crate) fn run_format<'py>(request: &FormatRequest<'_, 'py>) -> PyResult
{ - return Err(imported(py, LINT_ERRORS_MODULE, "LintError")? + return Err(registry::error(py, ErrorId::Lint)? .call1(("fixture-null formatting requires the project adapter",)) .map(pyo3::PyErr::from_value)?); } @@ -273,7 +271,7 @@ pub(crate) fn run_format<'py>(request: &FormatRequest<'_, 'py>) -> PyResult = file_dictionary(py, collected)?; let safe: Bound<'py, PyAny> = call_with( - &native(py, "safe_format_files")?, + &native(py, NativeAttr::SafeFormatFiles)?, &[ ("files", files.clone().into_any()), ("config", request.config.clone()), @@ -310,7 +308,7 @@ pub(crate) fn run_format<'py>(request: &FormatRequest<'_, 'py>) -> PyResult = overlaid(&files, &updated)?; let allow_suppressions: Bound<'py, PyAny> = request.config.getattr("allow_suppressions")?; - let violations: Vec> = native(py, "apply_lint_suppressions")? + let violations: Vec> = native(py, NativeAttr::ApplyLintSuppressions)? .call1((violations, &final_contents, allow_suppressions))? .extract()?; Ok(FormatOutcome { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/format_command/selection.rs b/crates/sqlbuild-python/src/bindings/_helpers/format_command/selection.rs index a46e43cc78..65ca9574ce 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/format_command/selection.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/format_command/selection.rs @@ -16,14 +16,10 @@ use sqlbuild_rules::lint_text::main::scan_fixture_null_candidates::scan_fixture_ use crate::bindings::_helpers::boundary::panics::value_error; use crate::bindings::_helpers::command_runtime::adapters::resolve_cli_adapter; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, within}; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const PLANNER_ERRORS_MODULE: &str = "sqlbuild._native"; -const DISCOVERY_MODELS_MODULE: &str = "sqlbuild._native"; -const UNIT_TEST_SELECTORS_MODULE: &str = "sqlbuild._native"; -const GRAPH_SELECTION_MODULE: &str = "sqlbuild._native"; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; const SELECTION_CODE: &str = "S007"; /// The adapter, the selected files when a selector scopes the run, and the discovered inputs. @@ -44,7 +40,7 @@ fn planner_error(py: Python<'_>, message: &str, code: &str) -> PyErr { let raised = (|| -> PyResult> { let keywords: Bound<'_, PyDict> = PyDict::new(py); keywords.set_item("code", code)?; - imported(py, PLANNER_ERRORS_MODULE, "PlannerInputError")?.call((message,), Some(&keywords)) + registry::error(py, ErrorId::PlannerInput)?.call((message,), Some(&keywords)) })(); match raised { Ok(instance) => PyErr::from_value(instance), @@ -77,7 +73,7 @@ fn discovered_adapter<'py>( ) -> PyResult> { let py: Python<'py> = project_dir.py(); let adapter_name: Bound<'py, PyAny> = call_with( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), @@ -156,14 +152,14 @@ fn model_paths( return Ok(paths); } let graph: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "build_project_graph")?, + ®istry::native(py, NativeAttr::BuildProjectGraph)?, &[ ("discovered_inputs", scope.discovered.clone()), ("adapter", scope.adapter.clone()), ], )?; let keys: Bound<'_, PyAny> = call_with( - &imported(py, GRAPH_SELECTION_MODULE, "select_project_graph")?, + ®istry::native(py, NativeAttr::SelectProjectGraph)?, &[ ("graph", graph.clone()), ("select", selectors(py, select)?), @@ -239,22 +235,22 @@ fn selected_contract_inputs<'py>( referenced_model_names: Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = project_dir.py(); - let native = |name: &str| imported(py, NATIVE_MODULE, name); + let native = |name: NativeAttr| registry::native(py, name); let project_config: Bound<'py, PyAny> = - native("load_project_config_file")?.call1((project_dir,))?; + native(NativeAttr::LoadProjectConfigFile)?.call1((project_dir,))?; let local_config: Bound<'py, PyAny> = - native("load_local_config_file")?.call1((project_dir,))?; + native(NativeAttr::LoadLocalConfigFile)?.call1((project_dir,))?; let disabled: Bound<'py, PyAny> = PyBool::new(py, false).to_owned().into_any(); let scope: Bound<'py, PyAny> = call_with( - &native("directory_snapshot_scope")?, + &native(NativeAttr::DirectorySnapshotScope)?, &[("project_dir", project_dir.clone())], )?; within(&scope, |_| { - let project_only = |name: &str| -> PyResult> { + let project_only = |name: NativeAttr| -> PyResult> { call_with(&native(name)?, &[("project_dir", project_dir.clone())]) }; call_with( - &imported(py, DISCOVERY_MODELS_MODULE, "DiscoveredProjectInputs")?, + ®istry::class(py, RecordId::DiscoveredProjectInputs)?, &[ ("project_config", project_config), ("local_config", local_config), @@ -262,7 +258,7 @@ fn selected_contract_inputs<'py>( ( "model_files", call_with( - &native("discover_model_files")?, + &native(NativeAttr::DiscoverModelFiles)?, &[ ("project_dir", project_dir.clone()), ("extract_implicit_alias_columns", disabled.clone()), @@ -273,14 +269,20 @@ fn selected_contract_inputs<'py>( ), ( "model_schema_files", - project_only("discover_model_schema_files")?, + project_only(NativeAttr::DiscoverModelSchemaFiles)?, + ), + ( + "schema_files", + project_only(NativeAttr::DiscoverSchemaFiles)?, + ), + ( + "source_files", + project_only(NativeAttr::DiscoverSourceFiles)?, ), - ("schema_files", project_only("discover_schema_files")?), - ("source_files", project_only("discover_source_files")?), ( "test_files", call_with( - &native("discover_test_files")?, + &native(NativeAttr::DiscoverTestFiles)?, &[ ("project_dir", project_dir.clone()), ("selected_paths", selected_test_paths), @@ -326,7 +328,7 @@ pub(crate) fn resolve_lint_inputs<'py>( } let disabled: Bound<'py, PyAny> = PyBool::new(py, false).to_owned().into_any(); let discovered: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[ ("project_dir", project_dir.clone()), ("sql_analysis_enabled_override", disabled.clone()), @@ -334,7 +336,7 @@ pub(crate) fn resolve_lint_inputs<'py>( ], )?; call_with( - &imported(py, UNIT_TEST_SELECTORS_MODULE, "reject_unit_test_selectors")?, + ®istry::native(py, NativeAttr::RejectUnitTestSelectors)?, &[ ("select", selectors(py, select)?), ("exclude", selectors(py, exclude)?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/freshness/freshness_command.rs b/crates/sqlbuild-python/src/bindings/_helpers/freshness/freshness_command.rs index cb2627250b..91ceb4d2b0 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/freshness/freshness_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/freshness/freshness_command.rs @@ -20,15 +20,11 @@ use crate::bindings::_helpers::freshness::observation::Observer; use crate::bindings::_helpers::freshness::state_io::{StateRead, read_latest}; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ERRORS_MODULE: &str = "sqlbuild._native"; -const REJECT_UNIT_TESTS_MODULE: &str = "sqlbuild._native"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; -const CONNECTION_MODULE: &str = "sqlbuild._native"; -const GRAPH_SELECTION_MODULE: &str = "sqlbuild._native"; -const UPSTREAM_MODULE: &str = "sqlbuild._native"; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; -const RELATION_LOOKUP_MODULE: &str = "sqlbuild.adapter.relations.main.relation_lookup"; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr}; +use crate::bindings::_helpers::records::registry; use sqlbuild_core::constants::SOURCE_FRESHNESS_TABLE_NAME as STATE_TABLE_NAME; const STALE_REQUIRES_STATE_MESSAGE: &str = "freshness --fail-on-stale requires --state"; const STALE_REQUIRES_STATE_CODE: &str = "C238"; @@ -52,7 +48,7 @@ fn user_error(py: Python<'_>, (message, code): (&str, &str)) -> PyErr { let raised = (|| { let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("code", code)?; - imported(py, ERRORS_MODULE, "CliUserError")?.call((message,), Some(&options)) + registry::error(py, ErrorId::CliUser)?.call((message,), Some(&options)) })(); match raised { Ok(error) => PyErr::from_value(error), @@ -76,7 +72,7 @@ fn source_names_for_keys( let py: Python<'_> = graph.py(); let source_type: Bound<'_, PyAny> = imported(py, COMPILE_TYPES_MODULE, "CompiledResourceType")?.getattr("SOURCE")?; - let expand: Bound<'_, PyAny> = imported(py, UPSTREAM_MODULE, "expand_project_upstream_keys")?; + let expand: Bound<'_, PyAny> = registry::native(py, NativeAttr::ExpandProjectUpstreamKeys)?; let upstream_deps: Bound<'_, PyAny> = graph.getattr("upstream_deps")?; for key in keys.try_iter()? { let key: Bound<'_, PyAny> = key?; @@ -103,7 +99,7 @@ fn graph_keys<'py>( ) -> PyResult> { let py: Python<'py> = graph.py(); let empty: Bound<'py, PyAny> = PyTuple::empty(py).into_any(); - imported(py, GRAPH_SELECTION_MODULE, "select_project_graph")?.call( + registry::native(py, NativeAttr::SelectProjectGraph)?.call( (), Some(&keywords( py, @@ -167,18 +163,17 @@ fn previous_records<'py>( ) }) .collect::>()?; - let lookup: Bound<'py, PyAny> = imported(py, RELATION_LOOKUP_MODULE, "build_relation_lookup")? - .call( - (), - Some(&keywords( - py, - &[ - ("adapter", adapter), - ("connection", connection), - ("locations", &PyTuple::new(py, locations)?.into_any()), - ], - )?), - )?; + let lookup: Bound<'py, PyAny> = host::entry(py, AdapterHost::BuildRelationLookup)?.call( + (), + Some(&keywords( + py, + &[ + ("adapter", adapter), + ("connection", connection), + ("locations", &PyTuple::new(py, locations)?.into_any()), + ], + )?), + )?; let read: StateRead<'py> = StateRead { connection: connection.clone(), execute: adapter.getattr("execute")?, @@ -212,7 +207,7 @@ fn observe( ) -> PyResult> { let py: Python<'_> = adapter.py(); let connection_config: Bound<'_, PyAny> = - imported(py, CONNECTION_MODULE, "resolve_project_connection_config")?.call( + registry::native(py, NativeAttr::ResolveProjectConnectionConfig)?.call( (), Some(&keywords( py, @@ -266,7 +261,7 @@ fn write_json_output( let options: Bound<'_, PyDict> = keywords(py, &[("json_output_path", json_output_path)])?; options.set_item("payload", format!("{payload}\n"))?; options.set_item("json_output", json_output)?; - imported(py, NATIVE_MODULE, "write_execution_json_output")?.call((), Some(&options))?; + registry::native(py, NativeAttr::WriteExecutionJsonOutput)?.call((), Some(&options))?; Ok(()) } @@ -286,11 +281,11 @@ fn run_freshness_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult directory if !directory.is_none() => directory, _ => py.import("pathlib")?.getattr("Path")?.call_method0("cwd")?, }; - let discovered: Bound<'_, PyAny> = imported(py, NATIVE_MODULE, "discover_project_inputs")? + let discovered: Bound<'_, PyAny> = registry::native(py, NativeAttr::DiscoverProjectInputs)? .call((), Some(&keywords(py, &[("project_dir", &project_dir)])?))?; let select: Bound<'_, PyAny> = request.getattr("select")?; let exclude: Bound<'_, PyAny> = request.getattr("exclude")?; - imported(py, REJECT_UNIT_TESTS_MODULE, "reject_unit_test_selectors")?.call( + registry::native(py, NativeAttr::RejectUnitTestSelectors)?.call( (), Some(&keywords( py, @@ -302,7 +297,7 @@ fn run_freshness_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult )?), )?; let adapter_name: Bound<'_, PyAny> = - imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?.call( + registry::native(py, NativeAttr::ResolveEffectiveAdapterName)?.call( (), Some(&keywords( py, @@ -313,7 +308,7 @@ fn run_freshness_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult )?), )?; let adapter: Bound<'_, PyAny> = resolve_cli_adapter(&adapter_name, &project_dir)?; - let graph: Bound<'_, PyAny> = imported(py, NATIVE_MODULE, "build_project_graph")?.call( + let graph: Bound<'_, PyAny> = registry::native(py, NativeAttr::BuildProjectGraph)?.call( (), Some(&keywords( py, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/freshness/freshness_observe.rs b/crates/sqlbuild-python/src/bindings/_helpers/freshness/freshness_observe.rs index d98cb58a74..8baefb7939 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/freshness/freshness_observe.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/freshness/freshness_observe.rs @@ -15,13 +15,14 @@ use crate::bindings::_helpers::freshness::observation::{ Observer, observation_record, observation_source, observe_adapter_batch, observe_configured, strategy, }; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::state_rows::freshness::{age_policy, equivalent}; use sqlbuild_core::freshness_text::main::freshness_status::freshness_status; use sqlbuild_core::freshness_text::models::{ FreshnessSourceResult, FreshnessStatus, StateComparison, }; -const RUN_SCOPE_MODULE: &str = "sqlbuild.runtime.observability.main.run_scope"; const ADAPTER_STRATEGY: &str = "adapter"; const UNAVAILABLE_REASON: &str = "unavailable"; const OBSERVATION_RUN_ID: &str = "freshness"; @@ -276,10 +277,7 @@ pub(crate) fn observe_freshness( let Some(run_id) = run_id else { return observe_sources(observer, &chosen, state); }; - let scope: Bound<'_, PyAny> = py - .import(RUN_SCOPE_MODULE)? - .getattr("run_scope")? - .call1((run_id,))?; + let scope: Bound<'_, PyAny> = host::entry(py, ExecutorHost::RunScope)?.call1((run_id,))?; scope.call_method0("__enter__")?; match observe_sources(observer, &chosen, state) { Ok(results) => { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/freshness/observation.rs b/crates/sqlbuild-python/src/bindings/_helpers/freshness/observation.rs index 531c0b7a83..92dcadea18 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/freshness/observation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/freshness/observation.rs @@ -8,16 +8,19 @@ use sqlbuild_state::freshness::models::QueryShape; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, within}; use crate::bindings::_helpers::freshness::support::{ - ADAPTER_MODELS, ADAPTER_TYPES, CONTRACT_MODELS, CONTRACT_TYPES, FRESHNESS_MODELS, - LIFECYCLE_MODULE, OBSERVABILITY_MODELS, OBSERVATION_ERROR, exception_text, freshness_error, - presumed_utc_datetime, presumed_utc_value, text, + OBSERVATION_ERROR, exception_text, freshness_error, presumed_utc_datetime, presumed_utc_value, + text, }; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::records::constants::{DATA_VERSION_FIELD, OBSERVED_AT_FIELD}; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::references::source_relations::render_source_relation; use crate::bindings::_helpers::state_rows::freshness::{normalize, version_hash}; use crate::bindings::_helpers::state_rows::timestamps::{in_utc, offset_aware}; -const NATIVE_RECORDS: &str = "sqlbuild._native"; const ADAPTER_STRATEGY: &str = "adapter"; const COLUMN_STRATEGY: &str = "column"; const DEFAULT_STRATEGY: &str = "sql"; @@ -62,7 +65,7 @@ pub(crate) fn observation_source<'py>( return Ok(None); } let config: Bound<'py, PyAny> = call_with( - &imported(py, CONTRACT_MODELS, "SourceFreshnessConfig")?, + ®istry::class(py, RecordId::SourceFreshnessConfig)?, &[("strategy", adapter_strategy(py)?)], )?; replaced_source(source, config).map(Some) @@ -78,7 +81,7 @@ fn replaced_source<'py>( } fn adapter_strategy(py: Python<'_>) -> PyResult> { - imported(py, CONTRACT_TYPES, "SourceFreshnessStrategy")?.getattr("ADAPTER") + registry::class(py, RecordId::SourceFreshnessStrategy)?.getattr("ADAPTER") } /// `OperationLifecycle(operation_kind="freshness", ...)` for one observation phase. @@ -88,7 +91,7 @@ fn lifecycle<'py>( item_count: Option, ) -> PyResult> { let attributes: Bound<'py, PyAny> = call_with( - &imported(py, OBSERVABILITY_MODELS, "OperationAttributes")?, + &host::entry(py, ExecutorHost::OperationAttributes)?, &[ ("phase", text(py, "observe")), ("strategy", text(py, strategy)), @@ -106,7 +109,7 @@ fn lifecycle<'py>( } options.push(("attributes", attributes)); call_with( - &imported(py, LIFECYCLE_MODULE, "OperationLifecycle")?, + &host::entry(py, ExecutorHost::OperationLifecycle)?, &options, ) } @@ -117,11 +120,7 @@ fn observation<'py>( (data_version, observed_at): (Bound<'py, PyAny>, Bound<'py, PyAny>), ) -> PyResult> { call_with( - &imported( - source_name.py(), - NATIVE_RECORDS, - "SourceFreshnessObservation", - )?, + ®istry::class(source_name.py(), RecordId::SourceFreshnessObservation)?, &[ ("source_name", source_name.clone()), ("strategy", strategy), @@ -309,12 +308,12 @@ pub(crate) fn unknown<'py>( ) -> PyResult> { let py: Python<'py> = source_name.py(); call_with( - &imported(py, NATIVE_RECORDS, "SourceFreshnessUnknown")?, + ®istry::class(py, RecordId::SourceFreshnessUnknown)?, &[ ("source_name", source_name.clone()), ( "reason", - imported(py, NATIVE_RECORDS, "SourceFreshnessUnknownReason")?.getattr(reason)?, + registry::class(py, RecordId::SourceFreshnessUnknownReason)?.getattr(reason)?, ), ("message", message), ], @@ -329,7 +328,7 @@ fn metadata_unknown<'py>( let py: Python<'py> = source_name.py(); let name: String = source_name.str()?.extract()?; let unavailable: Bound<'py, PyAny> = - imported(py, ADAPTER_TYPES, "TableFreshnessStatus")?.getattr("UNAVAILABLE")?; + host::entry(py, AdapterHost::TableFreshnessStatus)?.getattr("UNAVAILABLE")?; if metadata.getattr("status")?.eq(unavailable)? { let message: Bound<'py, PyAny> = metadata.getattr("message")?; let message: String = if message.is_truthy()? { @@ -362,7 +361,7 @@ fn metadata_observation<'py>( source_name, ( adapter_strategy(py)?, - imported(py, CONTRACT_TYPES, "SourceFreshnessValueKind")? + registry::class(py, RecordId::SourceFreshnessValueKind)? .call1((metadata.getattr("value_kind")?,))?, ), ( @@ -406,7 +405,7 @@ pub(crate) fn observe_adapter_batch<'py>( ) -> PyResult> { let py: Python<'py> = observer.adapter.py(); let batch_class: Bound<'py, PyAny> = - imported(py, NATIVE_RECORDS, "AdapterSourceFreshnessBatch")?; + registry::class(py, RecordId::AdapterSourceFreshnessBatch)?; if sources.is_empty() { return batch_class.call0(); } @@ -434,10 +433,10 @@ fn observe_adapter_batch_inner<'py>( ) -> PyResult> { let py: Python<'py> = observer.adapter.py(); let batch_class: Bound<'py, PyAny> = - imported(py, NATIVE_RECORDS, "AdapterSourceFreshnessBatch")?; + registry::class(py, RecordId::AdapterSourceFreshnessBatch)?; let unknown_sources: Bound<'py, PyDict> = PyDict::new(py); let requests: Bound<'py, PyDict> = PyDict::new(py); - let request_class: Bound<'py, PyAny> = imported(py, ADAPTER_MODELS, "TableFreshnessRequest")?; + let request_class: Bound<'py, PyAny> = host::entry(py, AdapterHost::TableFreshnessRequest)?; for source in sources { let name: Bound<'py, PyAny> = source.getattr("name")?; if let Some(message) = unsupported_message(&observer.adapter, source)? { @@ -471,7 +470,7 @@ fn observe_adapter_batch_inner<'py>( ("requests", PyTuple::new(py, unique.keys())?.into_any()), ], )?; - let metadata_class: Bound<'py, PyAny> = imported(py, ADAPTER_MODELS, "TableFreshnessMetadata")?; + let metadata_class: Bound<'py, PyAny> = host::entry(py, AdapterHost::TableFreshnessMetadata)?; let observations: Bound<'py, PyDict> = PyDict::new(py); for (name, request) in requests.iter() { let missing: Bound<'py, PyAny> = call_with( @@ -526,7 +525,7 @@ pub(crate) fn observation_record<'py>( &data_version.extract::()?, )?; call_with( - &imported(py, FRESHNESS_MODELS, "SourceFreshnessRecord")?, + ®istry::class(py, RecordId::SourceFreshnessRecord)?, &[ ("source_name", source_name), ("target_database", source.getattr("database")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/freshness/planning.rs b/crates/sqlbuild-python/src/bindings/_helpers/freshness/planning.rs index 637249be3c..5f7d89cc16 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/freshness/planning.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/freshness/planning.rs @@ -15,27 +15,28 @@ use crate::bindings::_helpers::freshness::observation::{ strategy, unknown, }; use crate::bindings::_helpers::freshness::state_io::{StateRead, read_latest}; -use crate::bindings::_helpers::freshness::support::{ - ADAPTER_EXCEPTIONS, EXCEPTIONS_MODULE, OBSERVATION_ERROR, text, -}; +use crate::bindings::_helpers::freshness::support::text; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_identity::graph::build_downstream_model_name_closure; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::state_rows::freshness::{age_policy, equivalent}; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_RECORDS: &str = "sqlbuild._native"; const ADAPTER_STRATEGY: &str = "adapter"; -const COMPILE_MODELS: &str = "sqlbuild._native"; const COMPILE_TYPES: &str = "sqlbuild.compiler.compile.types"; const NO_CONFIG_MESSAGE: &str = "no freshness config and adapter metadata unavailable"; /// The message of an `AdapterUserError` or a source freshness observation error, if it is one. fn handled_message<'py>(py: Python<'py>, error: &PyErr) -> PyResult>> { let value: Bound<'py, PyAny> = error.value(py).clone().into_any(); - if value.is_instance(&imported(py, ADAPTER_EXCEPTIONS, "AdapterUserError")?)? { + if value.is_instance(&host::entry(py, AdapterHost::AdapterUserError)?)? { return value.getattr("message").map(Some); } - if value.is_instance(&imported(py, EXCEPTIONS_MODULE, OBSERVATION_ERROR)?)? { + if value.is_instance(®istry::native( + py, + NativeAttr::SourceFreshnessObservationError, + )?)? { return Ok(Some(value.str()?.into_any())); } Ok(None) @@ -126,7 +127,7 @@ fn adapter_batch<'py>( unknown_sources.set_item(&name, unknown(&name, ("ERROR", message.clone()))?)?; } call_with( - &imported(py, NATIVE_RECORDS, "AdapterSourceFreshnessBatch")?, + ®istry::class(py, RecordId::AdapterSourceFreshnessBatch)?, &[("unknown", unknown_sources.into_any())], ) } @@ -224,11 +225,9 @@ pub(crate) fn build_direct_source_freshness_planning_result<'py>( let Some(message) = handled_message(py, &error)? else { return Err(error); }; - let is_observation_error: bool = !error.value(py).is_instance(&imported( - py, - ADAPTER_EXCEPTIONS, - "AdapterUserError", - )?)?; + let is_observation_error: bool = !error + .value(py) + .is_instance(&host::entry(py, AdapterHost::AdapterUserError)?)?; if is_observation_error && configuration_error(&message.extract::()?) { return Err(error); } @@ -271,11 +270,7 @@ pub(crate) fn build_direct_source_freshness_planning_result<'py>( } let unknown_names: Bound<'py, PyDict> = sorted_dict(py, &unknown_sources)?; call_with( - &imported( - py, - NATIVE_RECORDS_MODULE, - "DirectSourceFreshnessPlanningResult", - )?, + ®istry::class(py, RecordId::DirectSourceFreshnessPlanningResult)?, &[ ( "observed_records", @@ -308,7 +303,7 @@ fn downstream_models<'py>( ) -> PyResult> { let py: Python<'py> = source_name.py(); let key: Bound<'py, PyAny> = call_with( - &imported(py, COMPILE_MODELS, "CompiledObjectKey")?, + ®istry::class(py, RecordId::CompiledObjectKey)?, &[ ( "resource_type", @@ -351,7 +346,7 @@ pub(crate) fn build_direct_source_freshness_propagation_result<'py>( unknown_sources.set_item(name, models)?; } let error_status: Bound<'py, PyAny> = - imported(py, NATIVE_RECORDS, "SourceFreshnessAgeStatus")?.getattr("ERROR")?; + registry::class(py, RecordId::SourceFreshnessAgeStatus)?.getattr("ERROR")?; for (identity, status) in source_freshness .getattr("age_statuses")? .downcast::()? @@ -366,7 +361,7 @@ pub(crate) fn build_direct_source_freshness_propagation_result<'py>( errors.set_item(identity, models)?; } call_with( - &imported(py, NATIVE_RECORDS, "DirectSourceFreshnessPropagationResult")?, + ®istry::class(py, RecordId::DirectSourceFreshnessPropagationResult)?, &[ ("changed_source_model_names", changed.into_any()), ("unknown_source_model_names", unknown_sources.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/freshness/publication.rs b/crates/sqlbuild-python/src/bindings/_helpers/freshness/publication.rs index 8ee5b46af2..71dc47541b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/freshness/publication.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/freshness/publication.rs @@ -7,16 +7,10 @@ use pyo3::{pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, within, }; -use crate::bindings::_helpers::records::constants::NATIVE_MODULE; - -/// Module of the execution status enum. -const SCHEDULING_TYPES_MODULE: &str = "sqlbuild.executor.scheduling.types"; -/// Module of the run-scope context manager. -const RUN_SCOPE_MODULE: &str = "sqlbuild.runtime.observability.main.run_scope"; -/// Module of the operation lifecycle context manager. -const LIFECYCLE_MODULE: &str = "sqlbuild.runtime.observability.classes.operation_lifecycle"; -/// Module of the operation attributes record. -const OBSERVABILITY_MODELS_MODULE: &str = "sqlbuild.runtime.observability.models"; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; /// Observed records grouped by `(database, schema)` in first-seen order. type RecordsByLocation<'py> = Vec<( @@ -28,7 +22,7 @@ type RecordsByLocation<'py> = Vec<( fn successful_model_names<'py>(result: &Bound<'py, PyAny>) -> PyResult>> { let py: Python<'py> = result.py(); let success: Bound<'py, PyAny> = - imported(py, SCHEDULING_TYPES_MODULE, "ExecutionStatus")?.getattr("SUCCESS")?; + host::entry(py, ExecutorHost::ExecutionStatus)?.getattr("SUCCESS")?; let mut names: Vec> = Vec::new(); for model_result in result.getattr("model_results")?.try_iter()? { let model_result: Bound<'py, PyAny> = model_result?; @@ -157,7 +151,7 @@ fn eligible_records<'py>( /// `SourceFreshnessRenderers` bound to `adapter`. fn renderers<'py>(adapter: &Bound<'py, PyAny>) -> PyResult> { call_with( - &imported(adapter.py(), NATIVE_MODULE, "SourceFreshnessRenderers")?, + ®istry::class(adapter.py(), RecordId::SourceFreshnessRenderers)?, &[ ( "render_qualified_name", @@ -190,8 +184,8 @@ fn write_records<'py>( grouped: &RecordsByLocation<'py>, ) -> PyResult<()> { let py: Python<'py> = adapter.py(); - let cost_context: Bound<'py, PyAny> = imported(py, NATIVE_MODULE, "CostContext")?; - let write: Bound<'py, PyAny> = imported(py, NATIVE_MODULE, "write_source_freshness_records")?; + let cost_context: Bound<'py, PyAny> = registry::native(py, NativeAttr::CostContext)?; + let write: Bound<'py, PyAny> = registry::native(py, NativeAttr::WriteSourceFreshnessRecords)?; for ((database, schema), records) in grouped { let mut by_source: Vec<(Bound<'py, PyAny>, Vec>)> = Vec::new(); for record in records { @@ -251,20 +245,19 @@ pub(crate) fn append_freshness_records<'py>( if record_count == 0 { return Ok(()); } - let run_scope: Bound<'py, PyAny> = - imported(py, RUN_SCOPE_MODULE, "run_scope")?.call1((run_id,))?; + let run_scope: Bound<'py, PyAny> = host::entry(py, ExecutorHost::RunScope)?.call1((run_id,))?; within(&run_scope, |_| { let metadata: Bound<'py, PyDict> = PyDict::new(py); metadata.set_item("item_count", record_count)?; let attributes: Bound<'py, PyAny> = call_with( - &imported(py, OBSERVABILITY_MODELS_MODULE, "OperationAttributes")?, + &host::entry(py, ExecutorHost::OperationAttributes)?, &[ ("phase", PyString::new(py, "publish").into_any()), ("target_kind", PyString::new(py, "state_batch").into_any()), ], )?; let lifecycle: Bound<'py, PyAny> = call_with( - &imported(py, LIFECYCLE_MODULE, "OperationLifecycle")?, + &host::entry(py, ExecutorHost::OperationLifecycle)?, &[ ("operation_kind", PyString::new(py, "freshness").into_any()), ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/freshness/state_io.rs b/crates/sqlbuild-python/src/bindings/_helpers/freshness/state_io.rs index 308c987873..1d6b3465e9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/freshness/state_io.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/freshness/state_io.rs @@ -13,13 +13,13 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ }; use crate::bindings::_helpers::cursor::values::DATETIME_MODULE; use crate::bindings::_helpers::freshness::support::{ - FRESHNESS_MODELS, INPUT_ERROR, exception_text, freshness_error, optional_str, - presumed_utc_datetime, text, + INPUT_ERROR, exception_text, freshness_error, optional_str, presumed_utc_datetime, text, }; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::state_rows::state_tables::state_table_create_sql; use crate::bindings::_helpers::state_rows::timestamps::date_time_typed; -const NATIVE_RECORDS: &str = "sqlbuild._native"; const MISSING_SCHEMA_MESSAGE: &str = "source freshness table requires a target schema"; /// The qualified state table name the adapter renders for one target schema. @@ -110,7 +110,7 @@ pub(crate) struct StateRead<'py> { fn empty_set<'py>(schema: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = schema.py(); call_with( - &imported(py, NATIVE_RECORDS, "SourceFreshnessSet")?, + ®istry::class(py, RecordId::SourceFreshnessSet)?, &[ ("schema", schema.clone()), ("records", PyDict::new(py).into_any()), @@ -174,7 +174,7 @@ freshness table to regenerate source freshness state." records.set_item(record.getattr("identity")?, record)?; } call_with( - &imported(py, NATIVE_RECORDS, "SourceFreshnessSet")?, + ®istry::class(py, RecordId::SourceFreshnessSet)?, &[("schema", schema.clone()), ("records", records.into_any())], ) } @@ -195,7 +195,7 @@ fn row_record<'py>(row: &Bound<'py, PyAny>) -> PyResult> { let optional = |index: usize| -> PyResult> { optional_str(&row.get_item(index)?) }; call_with( - &imported(py, FRESHNESS_MODELS, "SourceFreshnessRecord")?, + ®istry::class(py, RecordId::SourceFreshnessRecord)?, &[ ("source_name", field(0)?), ("target_database", optional(1)?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/freshness/support.rs b/crates/sqlbuild-python/src/bindings/_helpers/freshness/support.rs index e448a148da..2f7e75a44e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/freshness/support.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/freshness/support.rs @@ -6,32 +6,23 @@ use pyo3::types::{PyDict, PyString}; use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::cursor::values::DATETIME_MODULE; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::state_rows::timestamps::{date_time_typed, in_utc, offset_aware}; -pub(crate) const EXCEPTIONS_MODULE: &str = "sqlbuild._native"; -pub(crate) const FRESHNESS_MODELS: &str = "sqlbuild._native"; -pub(crate) const CONTRACT_MODELS: &str = "sqlbuild._native"; -pub(crate) const CONTRACT_TYPES: &str = "sqlbuild._native"; -pub(crate) const ADAPTER_MODELS: &str = "sqlbuild.adapter.contract.models"; -pub(crate) const ADAPTER_TYPES: &str = "sqlbuild.adapter.contract.types"; -pub(crate) const ADAPTER_EXCEPTIONS: &str = "sqlbuild.adapter.contract.exceptions"; -pub(crate) const LIFECYCLE_MODULE: &str = - "sqlbuild.runtime.observability.classes.operation_lifecycle"; -pub(crate) const OBSERVABILITY_MODELS: &str = "sqlbuild.runtime.observability.models"; -pub(crate) const OBSERVATION_ERROR: &str = "SourceFreshnessObservationError"; -pub(crate) const INPUT_ERROR: &str = "SourceFreshnessInputError"; +pub(crate) const OBSERVATION_ERROR: NativeAttr = NativeAttr::SourceFreshnessObservationError; +pub(crate) const INPUT_ERROR: NativeAttr = NativeAttr::SourceFreshnessInputError; -/// `exceptions.(message)` as a raisable error, with an optional `__cause__`. +/// The native error `class(message)` as a raisable error, with an optional `__cause__`. pub(crate) fn freshness_error( py: Python<'_>, - (class, message): (&str, &str), + (class, message): (NativeAttr, &str), cause: Option, ) -> PyErr { - let error: PyErr = - match imported(py, EXCEPTIONS_MODULE, class).and_then(|class| class.call1((message,))) { - Ok(error) => PyErr::from_value(error), - Err(error) => return error, - }; + let error: PyErr = match registry::native(py, class).and_then(|class| class.call1((message,))) { + Ok(error) => PyErr::from_value(error), + Err(error) => return error, + }; if let Some(cause) = cause { error.set_cause(py, Some(cause)); } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/frontier/compile_frontier.rs b/crates/sqlbuild-python/src/bindings/_helpers/frontier/compile_frontier.rs index 36a0a2c68d..086309d934 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/frontier/compile_frontier.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/frontier/compile_frontier.rs @@ -4,13 +4,12 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyRes use pyo3::types::PyDict; use pyo3::{pyfunction, wrap_pyfunction}; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::frontier::stage_capture::write_stage_capture; - -const FRONTIER_TYPES_MODULE: &str = "sqlbuild._native"; -const RUN_BRIDGE_MODULE: &str = "sqlbuild.compiler.macro_bridge.classes.macro_bridge"; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; /// Produce the frontier object for `until`, capturing it on request. #[pyfunction] @@ -46,10 +45,10 @@ fn native_frontier<'py>( return native_stage.call0(); } let inputs_stage: Bound<'py, PyAny> = - imported(py, FRONTIER_TYPES_MODULE, "CompilerStage")?.getattr("COMPILE_PROJECT_INPUTS")?; + registry::class(py, RecordId::CompilerStage)?.getattr("COMPILE_PROJECT_INPUTS")?; if until.is(&inputs_stage) { return call_with( - &imported(py, RUN_BRIDGE_MODULE, "run_with_macro_bridge")?, + &host::entry(py, UserCodeHost::RunWithMacroBridge)?, &[("stage", python_stage.clone())], ); } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/frontier/encoder.rs b/crates/sqlbuild-python/src/bindings/_helpers/frontier/encoder.rs index 2f12ff6312..7ce3002cfb 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/frontier/encoder.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/frontier/encoder.rs @@ -9,7 +9,7 @@ use pyo3::types::{ PyStringMethods, PyTuple, }; -use sqlbuild_cache::hosted_shapes::constants::HOSTED_SHAPE_TYPES; +use sqlbuild_cache::hosted_shapes::main::captured_shape_name::captured_shape_name; use sqlbuild_cache::stage_capture::constants::{ CANONICAL_TYPES, DECODED_SEQUENCES, OMITTED_ATTRIBUTES, PROJECTED_ATTRIBUTES, RESERVED_KEYS, SHARED_MARKER, SHARED_MIN_CHARS, SKIPPED_SLOTS, UNORDERED_ATTRIBUTES, @@ -66,7 +66,7 @@ fn lookup<'a, T: Copy>(table: &'a [(&'a str, T)], name: &str) -> Option { /// The `module:qualname` a captured class is written as, natively hosted shapes keeping their Python name. fn captured_type_name(class: &Bound<'_, PyAny>) -> PyResult { let name: String = qualified_name(class)?; - Ok(lookup(&HOSTED_SHAPE_TYPES, &name).map_or(name, str::to_owned)) + Ok(captured_shape_name(&name).map_or(name, str::to_owned)) } /// `module:qualname` of a callable or class, as Python's `qualified_name` writes it. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/graph/algorithms.rs b/crates/sqlbuild-python/src/bindings/_helpers/graph/algorithms.rs index dbeb9c3956..28dd9b0966 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/graph/algorithms.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/graph/algorithms.rs @@ -1,12 +1,14 @@ -//! Breadth-first closure, edge inversion and path nodes over any Python-keyed edge mapping. - -use std::collections::VecDeque; +//! Closure, edge inversion and path nodes over any Python-keyed edge mapping. use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{ PyDict, PyDictMethods, PyFrozenSet, PyList, PyListMethods, PySet, PySetMethods, PyTuple, }; use pyo3::{pyfunction, wrap_pyfunction}; +use sqlbuild_core::graph_walk::main::breadth_first_reach::breadth_first_reach; +use sqlbuild_core::graph_walk::main::directed_path_nodes::directed_path_nodes; + +use crate::bindings::_helpers::graph::node_set::PyNodeSet; const BUILTINS_MODULE: &str = "builtins"; @@ -17,29 +19,32 @@ pub(crate) fn closure<'py>( (include_starts, max_depth): (bool, Option), ) -> PyResult> { let py: Python<'py> = edges.py(); - let visited: Bound<'py, PySet> = PySet::empty(py)?; + let mut visited: PyNodeSet<'py> = PyNodeSet(PySet::empty(py)?); if include_starts { for start in &starts { - visited.add(start)?; + visited.0.add(start)?; } } - let mut frontier: VecDeque<(Bound<'py, PyAny>, i64)> = - starts.into_iter().map(|start| (start, 0)).collect(); let none: Bound<'py, PyTuple> = PyTuple::empty(py); - while let Some((current, depth)) = frontier.pop_front() { - if max_depth.is_some_and(|maximum| depth >= maximum) { - continue; - } - for neighbor in edges.call_method1("get", (¤t, &none))?.try_iter()? { - let neighbor: Bound<'py, PyAny> = neighbor?; - if visited.contains(&neighbor)? { - continue; - } - visited.add(&neighbor)?; - frontier.push_back((neighbor, depth + 1)); - } - } - Ok(visited) + breadth_first_reach( + starts, + max_depth.map(|maximum| u64::try_from(maximum).unwrap_or(0)), + &mut visited, + |current| neighbours(edges, current, &none), + )?; + Ok(visited.0) +} + +/// The keys `edges.get(current, ())` lists. +fn neighbours<'py>( + edges: &Bound<'py, PyAny>, + current: &Bound<'py, PyAny>, + none: &Bound<'py, PyTuple>, +) -> PyResult>> { + edges + .call_method1("get", (current, none))? + .try_iter()? + .collect() } /// Keys within `max_depth` edges of any start, plus the starts when requested. @@ -130,35 +135,22 @@ fn path_nodes<'py>( downstream: &Bound<'py, PyAny>, ) -> PyResult>> { let py: Python<'py> = downstream.py(); - let reachable: Bound<'py, PySet> = closure(vec![start.clone()], downstream, (false, None))?; - if !reachable.contains(&end)? { - return Ok(None); - } let upstream: Bound<'py, PyDict> = inverted(downstream)?; - let upstream_from_end: Bound<'py, PySet> = PySet::empty(py)?; - let mut stack: Vec> = vec![end.clone()]; - while let Some(current) = stack.pop() { - if upstream_from_end.contains(¤t)? { - continue; - } - upstream_from_end.add(¤t)?; - let Some(parents) = upstream.get_item(¤t)? else { - continue; - }; - for parent in parents.try_iter()? { - let parent: Bound<'py, PyAny> = parent?; - if reachable.contains(&parent)? || parent.eq(&start)? { - stack.push(parent); - } - } + let none: Bound<'py, PyTuple> = PyTuple::empty(py); + let mut reachable: PyNodeSet<'py> = PyNodeSet(PySet::empty(py)?); + let mut path: PyNodeSet<'py> = PyNodeSet(PySet::empty(py)?); + let found: bool = directed_path_nodes( + (start, end), + ( + |current: &Bound<'py, PyAny>| neighbours(downstream, current, &none), + |current: &Bound<'py, PyAny>| neighbours(upstream.as_any(), current, &none), + ), + (&mut reachable, &mut path), + )?; + if !found { + return Ok(None); } - let nodes: Bound<'py, PyAny> = reachable - .call_method1("__and__", (&upstream_from_end,))? - .call_method1("__or__", (PySet::new(py, [&start, &end])?,))?; - Ok(Some(PyFrozenSet::new( - py, - nodes.try_iter()?.collect::>>()?, - )?)) + Ok(Some(PyFrozenSet::new(py, path.0.iter())?)) } pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/graph/execution_gates.rs b/crates/sqlbuild-python/src/bindings/_helpers/graph/execution_gates.rs index b8bdd2f925..d81e6923b7 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/graph/execution_gates.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/graph/execution_gates.rs @@ -8,12 +8,10 @@ use crate::bindings::_helpers::analysis::explicit_references::{ GateRow, HookRow, native_execution_gate_edges, }; use crate::bindings::_helpers::graph::keys::KeyCache; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const GRAPH_MODELS_MODULE: &str = "sqlbuild._native"; -const GRAPH_TYPES_MODULE: &str = "sqlbuild._native"; -const DISCOVERY_MODELS_MODULE: &str = "sqlbuild._native"; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const PATHLIB_MODULE: &str = "pathlib"; /// The model config keys whose entries are hooks, in lifecycle order. const MODEL_HOOK_KEYS: [&str; 2] = ["pre_hooks", "post_hooks"]; @@ -38,9 +36,7 @@ fn attached_audit_gate_edges<'py>( ) -> PyResult> { let (gates, _): (Vec, Vec) = native_execution_gate_edges(project)?; let cache: KeyCache<'py> = KeyCache::new(py)?; - let edge_type: Bound<'py, PyAny> = py - .import(GRAPH_MODELS_MODULE)? - .getattr("AttachedAuditGateEdge")?; + let edge_type: Bound<'py, PyAny> = registry::class(py, RecordId::AttachedAuditGateEdge)?; let mut edges: Vec> = Vec::with_capacity(gates.len()); for (audit_name, target, gated, read) in gates { edges.push(keyword_call( @@ -65,8 +61,8 @@ fn hook_read_edges<'py>( ) -> PyResult> { let (_, hooks): (Vec, Vec) = native_execution_gate_edges(project)?; let cache: KeyCache<'py> = KeyCache::new(py)?; - let edge_type: Bound<'py, PyAny> = py.import(GRAPH_MODELS_MODULE)?.getattr("HookReadEdge")?; - let hook_type: Bound<'py, PyAny> = py.import(GRAPH_TYPES_MODULE)?.getattr("HookReadType")?; + let edge_type: Bound<'py, PyAny> = registry::class(py, RecordId::HookReadEdge)?; + let hook_type: Bound<'py, PyAny> = registry::class(py, RecordId::HookReadType)?; let path_type: Bound<'py, PyAny> = py.import(PATHLIB_MODULE)?.getattr("Path")?; let mut edges: Vec> = Vec::with_capacity(hooks.len()); for (hook_name, gated, read, kind, path) in hooks { @@ -95,9 +91,7 @@ fn model_python_hook_names<'py>( py: Python<'py>, model: &Bound<'py, PyAny>, ) -> PyResult> { - let entry_type: Bound<'_, PyAny> = py - .import(DISCOVERY_MODELS_MODULE)? - .getattr("PythonHookEntry")?; + let entry_type: Bound<'_, PyAny> = registry::class(py, RecordId::PythonHookEntry)?; let values: Bound<'_, PyAny> = model.getattr("config")?.getattr("values")?; let mut names: Vec = Vec::new(); for hook_key in MODEL_HOOK_KEYS { @@ -130,8 +124,7 @@ fn sql_ref_key<'py>(py: Python<'py>, reference: &Bound<'py, PyAny>) -> PyResult< .getattr("CompiledResourceType")? .call1((reference.getattr("kind")?.getattr("value")?,))?; keyword_call( - &py.import(COMPILE_MODELS_MODULE)? - .getattr("CompiledObjectKey")?, + ®istry::class(py, RecordId::CompiledObjectKey)?, &[ ("resource_type", resource_type), ("name", reference.getattr("name")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/graph/keys.rs b/crates/sqlbuild-python/src/bindings/_helpers/graph/keys.rs index 87705c188c..2f4b5e27eb 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/graph/keys.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/graph/keys.rs @@ -1,9 +1,10 @@ //! `CompiledObjectKey`s for native `(resource type, name)` pairs, converted once per pair. +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods}; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; /// One `CompiledObjectKey` per native pair, so shared keys convert once. @@ -16,9 +17,7 @@ pub(crate) struct KeyCache<'py> { impl<'py> KeyCache<'py> { pub(crate) fn new(py: Python<'py>) -> PyResult { Ok(Self { - key_type: py - .import(COMPILE_MODELS_MODULE)? - .getattr("CompiledObjectKey")?, + key_type: registry::class(py, RecordId::CompiledObjectKey)?, resource_type: py .import(COMPILE_TYPES_MODULE)? .getattr("CompiledResourceType")?, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/graph/mod.rs b/crates/sqlbuild-python/src/bindings/_helpers/graph/mod.rs index af9d80ac3c..40cb605076 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/graph/mod.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/graph/mod.rs @@ -1,6 +1,7 @@ pub(crate) mod algorithms; mod execution_gates; pub(crate) mod keys; +pub(crate) mod node_set; pub(crate) mod project_graph; mod python_lifecycle; pub(crate) mod python_node_graph; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/graph/node_set.rs b/crates/sqlbuild-python/src/bindings/_helpers/graph/node_set.rs new file mode 100644 index 0000000000..488de612b0 --- /dev/null +++ b/crates/sqlbuild-python/src/bindings/_helpers/graph/node_set.rs @@ -0,0 +1,25 @@ +//! A Python set as the visited nodes of a native graph walk over Python keys. + +use pyo3::PyErr; +use pyo3::prelude::{Bound, PyAny}; +use pyo3::types::{PySet, PySetMethods}; +use sqlbuild_core::graph_walk::types::NodeSet; + +/// Python keys a walk visited, compared with Python hashing and equality. +pub(crate) struct PyNodeSet<'py>(pub(crate) Bound<'py, PySet>); + +impl<'py> NodeSet> for PyNodeSet<'py> { + type Error = PyErr; + + fn insert_new(&mut self, key: &Bound<'py, PyAny>) -> Result { + if self.0.contains(key)? { + return Ok(false); + } + self.0.add(key)?; + Ok(true) + } + + fn holds(&self, key: &Bound<'py, PyAny>) -> Result { + self.0.contains(key) + } +} diff --git a/crates/sqlbuild-python/src/bindings/_helpers/graph/python_lifecycle.rs b/crates/sqlbuild-python/src/bindings/_helpers/graph/python_lifecycle.rs index 1acdd6cbbc..dc4c16371c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/graph/python_lifecycle.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/graph/python_lifecycle.rs @@ -2,13 +2,14 @@ use std::collections::{BTreeSet, HashMap}; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyFrozenSet}; use pyo3::{pyfunction, wrap_pyfunction}; use sqlbuild_analysis::python_plan::main::run_lifecycle::run_lifecycle; use sqlbuild_analysis::python_plan::models::{LifecycleNode, RunLifecycle}; -const PYTHON_NODE_MODELS_MODULE: &str = "sqlbuild._native"; const EXTERNAL_CONNECTION_MODE: &str = "external"; /// `build_python_sql_run_lifecycle(*, selection, python_graph)`: a `PythonSqlRunLifecyclePlan`. @@ -60,9 +61,7 @@ fn build_python_sql_run_lifecycle<'py>( "read_side_python_node_names", PyFrozenSet::new(py, lifecycle.read_side_names)?, )?; - py.import(PYTHON_NODE_MODELS_MODULE)? - .getattr("PythonSqlRunLifecyclePlan")? - .call((), Some(&arguments)) + registry::class(py, RecordId::PythonSqlRunLifecyclePlan)?.call((), Some(&arguments)) } fn lifecycle_node(node: &Bound<'_, PyAny>) -> PyResult { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/graph/python_node_graph.rs b/crates/sqlbuild-python/src/bindings/_helpers/graph/python_node_graph.rs index c5cf6c65d9..e011134253 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/graph/python_node_graph.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/graph/python_node_graph.rs @@ -1,6 +1,5 @@ //! The executable Python-node graph built from discovered project inputs. -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class_at; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyFrozenSet, PyString, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; @@ -8,17 +7,15 @@ use sqlbuild_analysis::python_selection::main::python_node_graph_indexes::python use sqlbuild_analysis::python_selection::models::{InventoryIndexes, InventoryNode}; use crate::bindings::_helpers::boundary::keyword_request::Request; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::discovery::imported_modules::provider_usages; use crate::bindings::_helpers::graph::python_selection::resolve_python_sql_run_selection; use crate::bindings::_helpers::graph::views::compiled_graph_keys; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const USER_INVENTORY_MODULE: &str = "sqlbuild.compiler.python_nodes._helpers.user_inventory"; -const NODE_MODELS_MODULE: &str = "sqlbuild._native"; -const NODE_TYPES_MODULE: &str = "sqlbuild.compiler.python_nodes.types"; -const SQL_REF_MODULE: &str = "sqlbuild.python_nodes.models"; const NAME_KEY: &str = "name"; /// One discovered function collection, its node kind and whether it carries documentation. @@ -57,12 +54,12 @@ fn kind_parts<'py>( ) -> PyResult<(Bound<'py, PyDict>, &'static str, Bound<'py, PyAny>)> { let config: Bound<'py, PyDict> = PyDict::new(py); let get = |name: &str| attribute_value(discovered, name); - let metadata = |class: &str, fields: &[&str]| -> PyResult> { + let metadata = |class: RecordId, fields: &[&str]| -> PyResult> { let pairs: Vec<(&str, Bound<'py, PyAny>)> = fields .iter() .map(|field| Ok((*field, get(field)?))) .collect::>()?; - call_with(&shape_class_at(py, NODE_MODELS_MODULE, class)?, &pairs) + call_with(®istry::class(py, class)?, &pairs) }; Ok(match kind { "LOADER" => { @@ -73,7 +70,7 @@ fn kind_parts<'py>( config.set_item("unique_key", get("unique_key")?)?; config.set_item("write_strategy", optional_value(&get("write_strategy")?)?)?; let record = metadata( - "DiscoveredPythonLoaderMetadata", + RecordId::DiscoveredPythonLoaderMetadata, &[ "destination", "write_strategy", @@ -89,20 +86,20 @@ fn kind_parts<'py>( "TASK" => ( config, "task", - metadata("DiscoveredPythonTaskMetadata", &["retry"])?, + metadata(RecordId::DiscoveredPythonTaskMetadata, &["retry"])?, ), "ASSET" => { config.set_item("columns", get("columns")?)?; config.set_item("column_lineage", get("column_lineage")?)?; let record = metadata( - "DiscoveredPythonAssetMetadata", + RecordId::DiscoveredPythonAssetMetadata, &["columns", "column_lineage", "retry"], )?; (config, "asset", record) } _ => { config.set_item("severity", get("severity")?.getattr("value")?)?; - let record = metadata("DiscoveredPythonCheckMetadata", &["severity"])?; + let record = metadata(RecordId::DiscoveredPythonCheckMetadata, &["severity"])?; (config, "check", record) } }) @@ -180,8 +177,7 @@ fn upstream_names( name, )?; } - let dependency_key: Bound<'_, PyAny> = - imported(py, USER_INVENTORY_MODULE, "python_node_dependency_key")?; + let dependency_key: Bound<'_, PyAny> = host::entry(py, UserCodeHost::PythonNodeDependencyKey)?; nodes .iter() .map(|node| { @@ -224,10 +220,10 @@ pub(crate) fn python_node_graph<'py>( providers.set_item(provider.getattr("name")?, provider)?; } let vocabulary: Vocabulary<'py> = Vocabulary { - node: imported(py, NODE_MODELS_MODULE, "DiscoveredPythonNode")?, - kinds: imported(py, NODE_TYPES_MODULE, "PythonNodeKind")?, - sql_ref: imported(py, SQL_REF_MODULE, "SqlResourceRef")?, - identity: imported(py, USER_INVENTORY_MODULE, "python_node_identity")?, + node: registry::class(py, RecordId::DiscoveredPythonNode)?, + kinds: host::entry(py, UserCodeHost::PythonNodeKind)?, + sql_ref: host::entry(py, UserCodeHost::SqlResourceRef)?, + identity: host::entry(py, UserCodeHost::PythonNodeIdentity)?, providers, }; let mut nodes: Vec> = Vec::new(); @@ -258,8 +254,7 @@ fn graph_record<'py>( tags, paths, } = indexes; - let edge_class: Bound<'py, PyAny> = - imported(py, NATIVE_RECORDS_MODULE, "PythonNodeDependencyEdge")?; + let edge_class: Bound<'py, PyAny> = registry::class(py, RecordId::PythonNodeDependencyEdge)?; let dependency_edges: Vec> = edges .into_iter() .map(|(upstream_name, downstream_name)| { @@ -302,7 +297,7 @@ fn graph_record<'py>( by_selector.set_item(format!("{kind}:{}", name.str()?), node)?; } call_with( - &imported(py, NODE_MODELS_MODULE, "PythonNodeGraph")?, + ®istry::class(py, RecordId::PythonNodeGraph)?, &[ ("nodes", PyTuple::new(py, nodes)?.into_any()), ( @@ -330,7 +325,7 @@ pub(crate) fn run_selection_from_inputs<'py>( let (sql_keys, python_node_names) = resolve_python_sql_run_selection(project_graph, &python_graph, select, exclude)?; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "PythonSqlRunSelection")?, + ®istry::class(py, RecordId::PythonSqlRunSelection)?, &[ ("sql_keys", compiled_graph_keys(py, sql_keys)?.into_any()), ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/graph/python_selection.rs b/crates/sqlbuild-python/src/bindings/_helpers/graph/python_selection.rs index ef06bf1fa8..b8b1d112d7 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/graph/python_selection.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/graph/python_selection.rs @@ -17,10 +17,9 @@ use sqlbuild_analysis::python_selection::models::{ }; use crate::bindings::_helpers::boundary::panics::compiler_guard; -use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::graph::project_graph::NativeProjectGraph; - -const PLANNER_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; type NameRows = Vec<(String, Vec)>; /// `(name, tags, POSIX relative path, upstream node names)` of one discovered node. @@ -40,8 +39,7 @@ fn planner_error(py: Python<'_>, error: SelectorError) -> PyErr { let keywords: Bound<'_, PyDict> = PyDict::new(py); keywords.set_item("code", error.code)?; keywords.set_item("help", error.help)?; - imported(py, PLANNER_EXCEPTIONS_MODULE, "PlannerInputError")? - .call((error.message,), Some(&keywords)) + registry::error(py, ErrorId::PlannerInput)?.call((error.message,), Some(&keywords)) })(); match raised { Ok(instance) => PyErr::from_value(instance), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/graph/views.rs b/crates/sqlbuild-python/src/bindings/_helpers/graph/views.rs index 3ea882c339..b3d16357ad 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/graph/views.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/graph/views.rs @@ -12,8 +12,8 @@ use sqlbuild_analysis::graph::models::{GraphIndexes, ProjectGraph}; use crate::bindings::_helpers::boundary::panics::compiler_guard; use crate::bindings::_helpers::graph::keys::{KeyCache, native_key}; use crate::bindings::_helpers::graph::project_graph::NativeProjectGraph; - -const GRAPH_MODELS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; /// The native graph's `(resource type, name)` pair for `key`. #[pyfunction] @@ -84,9 +84,7 @@ fn graph_views<'py>(py: Python<'py>, graph: &ProjectGraph) -> PyResult = Bound<'py, PyAny>; @@ -20,8 +18,6 @@ pub(crate) fn compiled_model<'py>( macro_deps: &Object<'py>, ) -> PyResult> { let py: Python<'py> = model_input.py(); - let native = py.import(NATIVE_MODULE)?; - let models = py.import(COMPILE_MODELS_MODULE)?; let types = py.import(COMPILE_TYPES_MODULE)?; let model_file: Object<'py> = model_input.getattr("model_file")?; let model_name: Object<'py> = model_file.getattr("file_path")?.getattr("stem")?; @@ -32,8 +28,7 @@ pub(crate) fn compiled_model<'py>( let mut star_resolved: Object<'py> = false_object(py); let mut diagnostics: Object<'py> = PyTuple::empty(py).into_any(); let mut validated: Object<'py> = false_object(py); - let mut proof: Object<'py> = native - .getattr("dynamic_contract_proof")? + let mut proof: Object<'py> = registry::native(py, NativeAttr::DynamicContractProof)? .call1((analysis.getattr("standalone_proof")?,))?; let session: Object<'py> = analysis.getattr("session")?; if !session.is_none() { @@ -42,9 +37,8 @@ pub(crate) fn compiled_model<'py>( arguments.set_item("session", session)?; arguments.set_item("position", position)?; arguments.set_item("placeholders", &none)?; - let outcome: Object<'py> = native - .getattr("session_outcome_analysis")? - .call((), Some(&arguments))?; + let outcome: Object<'py> = + registry::native(py, NativeAttr::SessionOutcomeAnalysis)?.call((), Some(&arguments))?; let result: Object<'py> = outcome.getattr("polyglot_analysis")?; if result.getattr("analysis_succeeded")?.is_truthy()? { inferred = result.getattr("columns")?; @@ -76,7 +70,7 @@ pub(crate) fn compiled_model<'py>( has_star = true_object(py); } let resource_type: Object<'py> = types.getattr("CompiledResourceType")?; - let key_class: Object<'py> = models.getattr("CompiledObjectKey")?; + let key_class: Object<'py> = registry::class(py, RecordId::CompiledObjectKey)?; let mut dependency_keys: Vec> = Vec::new(); for dependency in deps.try_iter()? { let (kind, name): (Object<'py>, Object<'py>) = dependency?.extract()?; @@ -97,7 +91,7 @@ pub(crate) fn compiled_model<'py>( ], )?; call( - &models.getattr("CompiledModel")?, + ®istry::class(py, RecordId::CompiledModel)?, &[ ("key", key), ("deps", PyTuple::new(py, dependency_keys)?.into_any()), @@ -161,8 +155,7 @@ fn destination<'py>(config: &Object<'py>, model_name: &Object<'py>) -> PyResult< }; let none: Object<'py> = py.None().into_bound(py); call( - &py.import(COMPILE_MODELS_MODULE)? - .getattr("CompiledRelationLocation")?, + ®istry::class(py, RecordId::CompiledRelationLocation)?, &[ ( "database", @@ -183,9 +176,7 @@ fn fallback_columns<'py>(rows: &Object<'py>) -> PyResult> { if rows.is_none() { return Ok(rows.clone()); } - let column_class: Object<'py> = py - .import(COMPILE_MODELS_MODULE)? - .getattr("InferredColumn")?; + let column_class: Object<'py> = registry::class(py, RecordId::InferredColumn)?; let nullability: Object<'py> = py .import(LINEAGE_TYPES_MODULE)? .getattr("InferredNullability")?; @@ -263,17 +254,13 @@ fn located<'py>( )? .into_any() }; - let located: Object<'py> = py - .import(NATIVE_MODULE)? - .getattr("locate_model_binding_diagnostics")? + let located: Object<'py> = registry::native(py, NativeAttr::LocateModelBindingDiagnostics)? .call1((PyList::new(py, rows)?, texts, expansion_row))?; - let models = py.import(COMPILE_MODELS_MODULE)?; let types = py.import(COMPILE_TYPES_MODULE)?; - let diagnostic_class: Object<'py> = models.getattr("CompilerDiagnostic")?; - let location_class: Object<'py> = py - .import(CONTRACT_MODELS_MODULE)? - .getattr("SourceLocation")?; - let phase: Object<'py> = shape_class(types.as_any(), "DiagnosticPhase")?.getattr("COMPILE")?; + let diagnostic_class: Object<'py> = registry::class(py, RecordId::CompilerDiagnostic)?; + let location_class: Object<'py> = registry::class(py, RecordId::SourceLocation)?; + let phase: Object<'py> = + registry::class(types.py(), RecordId::DiagnosticPhase)?.getattr("COMPILE")?; let model: Object<'py> = types.getattr("CompiledResourceType")?.getattr("MODEL")?; let name: Object<'py> = model_file.getattr("file_path")?.getattr("stem")?; let mut built: Vec> = Vec::new(); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/init_command/command.rs b/crates/sqlbuild-python/src/bindings/_helpers/init_command/command.rs index 9b1c3c8348..2e3bc7a43a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/init_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/init_command/command.rs @@ -16,16 +16,16 @@ use sqlbuild_core::init_command::main::render_init_summary::render_init_summary; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; use crate::bindings::_helpers::init_command::scaffold::scaffold_blank_project; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ERRORS_MODULE: &str = "sqlbuild._native"; const EXISTS_MESSAGE: &str = "sqlbuild_project.toml already exists in this directory"; fn exists_error(py: Python<'_>) -> PyErr { let raised = (|| { let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("code", "C900")?; - imported(py, ERRORS_MODULE, "CliUserError")?.call((EXISTS_MESSAGE,), Some(&options)) + registry::error(py, ErrorId::CliUser)?.call((EXISTS_MESSAGE,), Some(&options)) })(); match raised { Ok(error) => PyErr::from_value(error), @@ -52,7 +52,7 @@ fn run_init_command(py: Python<'_>, project_dir: Option>) -> Py let base_path: PathBuf = base_dir.extract()?; scaffold_blank_project(py, &base_path, &init_project_toml(&project_name))?; call_with( - &imported(py, NATIVE_MODULE, "update_sqlbuild_skills")?, + ®istry::native(py, NativeAttr::UpdateSqlbuildSkills)?, &[("project_dir", base_dir)], )?; let style: CliStyle = CliStyle { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_classification.rs b/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_classification.rs index 4e60e02c91..2e7bc07f8d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_classification.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_classification.rs @@ -24,6 +24,7 @@ use crate::bindings::_helpers::janitor::janitor_keys::{ Folded, Key, SchemaKey, archive_timestamp, days, folded_schema, optional, record, relation_age_timestamp, sorted_schemas, text, timestamp_fields, tuple, utc_datetime, }; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::planner_scenario::artifacts::is_scenario_artifact_physical_name; use crate::bindings::_helpers::relation_naming::fitting::shared_error; @@ -53,7 +54,7 @@ impl<'py> Candidate<'py> { let py: Python<'py> = self.relation.py(); record( py, - "JanitorDeleteCandidate", + ExecutorHost::JanitorDeleteCandidate, &[ ("key", self.key.to_python(py)?), ("relation", self.relation.clone()), @@ -292,7 +293,7 @@ pub(crate) fn classify_target_schemas<'py>( } result.skipped_schemas.push(record( py, - "JanitorSkippedSchema", + ExecutorHost::JanitorSkippedSchema, &[ ("database", optional(py, schema_key.0.as_deref())?), ("schema", optional(py, schema_key.1.as_deref())?), @@ -344,7 +345,7 @@ fn archived_relation<'py>( if let Some(original_key) = original_key { pairs.push(("original_key", original_key.to_python(py)?)); } - record(py, "JanitorArchivedRelation", &pairs) + record(py, ExecutorHost::JanitorArchivedRelation, &pairs) } fn blocked_record<'py>( @@ -368,7 +369,7 @@ fn blocked_record<'py>( .unwrap_or_default(); record( py, - "JanitorBlockedSchema", + ExecutorHost::JanitorBlockedSchema, &[ ("database", optional(py, schema.schema.0.as_deref())?), ("schema", optional(py, schema.schema.1.as_deref())?), @@ -408,7 +409,7 @@ fn archive_candidate<'py>( }; let record = record( py, - "JanitorArchiveCandidate", + ExecutorHost::JanitorArchiveCandidate, &[ ("key", candidate.key.to_python(py)?), ("relation", candidate.relation.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_command.rs b/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_command.rs index d1f820dbb8..d573d76e06 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_command.rs @@ -27,11 +27,9 @@ use crate::bindings::_helpers::janitor::janitor_phases::{ }; use crate::bindings::_helpers::janitor::janitor_plan::{janitor_plan_text, janitor_result_counts}; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ERRORS_MODULE: &str = "sqlbuild._native"; -const PREVIEW_TARGET_MODULE: &str = "sqlbuild._native"; -const TARGET_NAME_MODULE: &str = "sqlbuild._native"; const COMMAND_NAME: &str = "janitor"; fn imported<'py>(py: Python<'py>, module: &str, name: &str) -> PyResult> { @@ -53,7 +51,7 @@ fn settings_error(py: Python<'_>, error: JanitorSettingsError) -> PyErr { let raised = (|| { let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("code", error.code)?; - imported(py, ERRORS_MODULE, "CliUserError")?.call((error.message,), Some(&options)) + registry::error(py, ErrorId::CliUser)?.call((error.message,), Some(&options)) })(); match raised { Ok(error) => PyErr::from_value(error), @@ -79,7 +77,7 @@ fn invocation<'py>(py: Python<'py>, request: &Bound<'py, PyAny>) -> PyResult directory, _ => py.import("pathlib")?.getattr("Path")?.call_method0("cwd")?, }; - let discovered: Bound<'py, PyAny> = imported(py, NATIVE_MODULE, "discover_project_inputs")? + let discovered: Bound<'py, PyAny> = registry::native(py, NativeAttr::DiscoverProjectInputs)? .call((), Some(&keywords(py, &[("project_dir", &project_dir)])?))?; let as_target: Bound<'py, PyAny> = request.getattr("as_target")?; let preview: Bound<'py, PyDict> = keywords( @@ -90,9 +88,9 @@ fn invocation<'py>(py: Python<'py>, request: &Bound<'py, PyAny>) -> PyResult = request.getattr("selected_target")?; - let active_target: Bound<'py, PyAny> = imported(py, TARGET_NAME_MODULE, "resolve_target_name")? + let active_target: Bound<'py, PyAny> = registry::native(py, NativeAttr::ResolveTargetName)? .call( (), Some(&keywords( @@ -117,7 +115,7 @@ fn invocation<'py>(py: Python<'py>, request: &Bound<'py, PyAny>) -> PyResult( ), ], )?; - imported(py, NATIVE_MODULE, "JanitorSettings")?.call((), Some(&options)) + registry::class(py, RecordId::JanitorSettings)?.call((), Some(&options)) } /// Ask for the exact confirmation phrase; an interrupt cancels. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_facts.rs b/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_facts.rs index 2b6a791f75..338b9bc580 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_facts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_facts.rs @@ -13,20 +13,16 @@ use crate::bindings::_helpers::janitor::janitor_keys::{ sorted_schemas, text, tuple, }; -const QUERY_ARTIFACT_LIFECYCLE: (&str, &str) = ( - "sqlbuild.executor.diff.classes.query_artifact_lifecycle", - "QueryDiffArtifactLifecycle", -); -const RELATION_LOOKUP: (&str, &str) = ( - "sqlbuild.adapter.relations.main.relation_lookup", - "build_relation_lookup", -); -const LOG_DEBUG_EVENT: (&str, &str) = ( - "sqlbuild.diagnostics.main.log_debug_event", - "log_debug_event", -); -const READ_LATEST_FINGERPRINTS: (&str, &str) = ("sqlbuild._native", "read_latest_fingerprints"); +const QUERY_ARTIFACT_LIFECYCLE: ExecutorHost = ExecutorHost::QueryDiffArtifactLifecycle; +const RELATION_LOOKUP: AdapterHost = AdapterHost::BuildRelationLookup; +const LOG_DEBUG_EVENT: ExecutorHost = ExecutorHost::LogDebugEvent; +const READ_LATEST_FINGERPRINTS: NativeAttr = NativeAttr::ReadLatestFingerprints; const EXECUTION_LOGGER: &str = "sqlbuild.execution"; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use sqlbuild_core::constants::FINGERPRINT_TABLE_NAME; use sqlbuild_core::constants::SOURCE_FRESHNESS_TABLE_NAME; @@ -245,7 +241,7 @@ fn relation_lookup<'py>( )?); } call_with( - &imported(py, RELATION_LOOKUP.0, RELATION_LOOKUP.1)?, + &host::entry(py, RELATION_LOOKUP)?, &[ ("adapter", warehouse.adapter.clone()), ("connection", warehouse.connection.clone()), @@ -300,7 +296,7 @@ fn tracked_relation_keys( (database.as_deref(), schema, FINGERPRINT_TABLE_NAME), )?; let read: PyResult> = call_with( - &imported(py, READ_LATEST_FINGERPRINTS.0, READ_LATEST_FINGERPRINTS.1)?, + ®istry::native(py, READ_LATEST_FINGERPRINTS)?, &[ ("connection", warehouse.connection.clone()), ("execute", warehouse.adapter.getattr("execute")?), @@ -326,7 +322,7 @@ fn tracked_relation_keys( Ok(set) => set, Err(error) if error.is_instance_of::(py) => { call_with( - &imported(py, LOG_DEBUG_EVENT.0, LOG_DEBUG_EVENT.1)?, + &host::entry(py, LOG_DEBUG_EVENT)?, &[ ( "logger", @@ -448,7 +444,7 @@ pub(crate) fn direct_state_prune_candidates<'py>( )?; candidates.push(record( py, - "JanitorDirectStatePruneCandidate", + ExecutorHost::JanitorDirectStatePruneCandidate, &[ ("database", optional(py, database.as_deref())?), ("schema", text(py, schema)), @@ -472,8 +468,7 @@ pub(crate) fn query_diff_artifact_candidates<'py>( now: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = warehouse.py(); - let lifecycle: Bound<'py, PyAny> = - imported(py, QUERY_ARTIFACT_LIFECYCLE.0, QUERY_ARTIFACT_LIFECYCLE.1)?; + let lifecycle: Bound<'py, PyAny> = host::entry(py, QUERY_ARTIFACT_LIFECYCLE)?; let mut sorted: Vec<(Option, String)> = schemas.to_vec(); sorted.sort_by(|left, right| { (left.0.as_deref().unwrap_or(""), left.1.as_str()) @@ -496,7 +491,7 @@ pub(crate) fn query_diff_artifact_candidates<'py>( let key: Key = Key::of(&artifact?)?; candidates.push(record( py, - "JanitorQueryDiffArtifactCandidate", + ExecutorHost::JanitorQueryDiffArtifactCandidate, &[("key", key.to_python(py)?)], )?); } @@ -521,7 +516,7 @@ pub(crate) fn skipped_relation<'py>( let py: Python<'py> = relation.py(); record( py, - "JanitorSkippedRelation", + ExecutorHost::JanitorSkippedRelation, &[ ("key", key.to_python(py)?), ("relation", relation.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_keys.rs b/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_keys.rs index 8f18cf46da..e60fdff466 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_keys.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_keys.rs @@ -7,8 +7,9 @@ use pyo3::types::{PyString, PyTuple}; use sqlbuild_analysis::janitor_planning::models::ArchiveTimestamp; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; -pub(crate) const JANITOR_MODELS: &str = "sqlbuild.executor.janitor.models"; const DATETIME_MODULE: &str = "datetime"; /// `(database, schema)` as spelled. @@ -58,7 +59,7 @@ impl Key { pub(crate) fn to_python<'py>(&self, py: Python<'py>) -> PyResult> { record( py, - "JanitorRelationKey", + ExecutorHost::JanitorRelationKey, &[ ("database", optional(py, self.database.as_deref())?), ("schema", optional(py, self.schema.as_deref())?), @@ -139,10 +140,10 @@ pub(crate) fn tuple<'py>( /// `sqlbuild.executor.janitor.models.(**pairs)`. pub(crate) fn record<'py>( py: Python<'py>, - class_name: &str, + class_name: ExecutorHost, pairs: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - call_with(&imported(py, JANITOR_MODELS, class_name)?, pairs) + call_with(&host::entry(py, class_name)?, pairs) } fn utc(py: Python<'_>) -> PyResult> { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_old_names.rs b/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_old_names.rs index 083173283f..031744d937 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_old_names.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_old_names.rs @@ -7,7 +7,7 @@ use pyo3::types::{PyDict, PyDictMethods}; use sqlbuild_analysis::janitor_planning::main::migration_origin_keys::migration_origin_keys; use sqlbuild_analysis::janitor_planning::main::old_name_request_matches::old_name_request_matches; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::janitor::janitor_facts::{ SchemaRelations, Warehouse, model_destinations, }; @@ -15,13 +15,13 @@ use crate::bindings::_helpers::janitor::janitor_keys::{ Folded, Key, SchemaKey, optional, record, sorted_schemas, text, tuple, }; -const MIGRATION_TYPES: &str = "sqlbuild._native"; -const IS_COMPATIBILITY_VIEW: (&str, &str) = ( - "sqlbuild.executor.old_name_views._helpers.janitor", - "is_compatibility_view", -); -const READ_HISTORIES: (&str, &str) = ("sqlbuild._native", "read_old_name_view_histories"); -const STORED_COLUMNS: (&str, &str) = ("sqlbuild._native", "stored_old_name_view_columns"); +const IS_COMPATIBILITY_VIEW: ExecutorHost = ExecutorHost::IsCompatibilityView; +const READ_HISTORIES: NativeAttr = NativeAttr::ReadOldNameViewHistories; +const STORED_COLUMNS: NativeAttr = NativeAttr::StoredOldNameViewColumns; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use sqlbuild_core::constants::MIGRATION_TABLE_NAME; use sqlbuild_core::constants::OLD_NAME_VIEW_TABLE_NAME; const MIGRATE_FROM_CONFIG_KEY: &str = "migrate_from"; @@ -33,7 +33,7 @@ pub(crate) struct OldNamePlanning<'py> { } fn drop_reason<'py>(py: Python<'py>, member: &str) -> PyResult> { - imported(py, MIGRATION_TYPES, "OldNameViewDropReason")?.getattr(member) + registry::class(py, RecordId::OldNameViewDropReason)?.getattr(member) } fn status<'py>( @@ -44,7 +44,7 @@ fn status<'py>( } fn status_is(py: Python<'_>, value: &Bound<'_, PyAny>, member: &str) -> PyResult { - value.eq(imported(py, MIGRATION_TYPES, "OldNameViewStatus")?.getattr(member)?) + value.eq(registry::class(py, RecordId::OldNameViewStatus)?.getattr(member)?) } fn read_histories<'py>( @@ -74,7 +74,7 @@ fn read_histories<'py>( continue; } let stored_columns: Bound<'py, PyAny> = call_with( - &imported(py, STORED_COLUMNS.0, STORED_COLUMNS.1)?, + ®istry::native(py, STORED_COLUMNS)?, &[ ("adapter", warehouse.adapter.clone()), ("connection", warehouse.connection.clone()), @@ -83,7 +83,7 @@ fn read_histories<'py>( ], )?; let read: Bound<'py, PyAny> = call_with( - &imported(py, READ_HISTORIES.0, READ_HISTORIES.1)?, + ®istry::native(py, READ_HISTORIES)?, &[ ("connection", warehouse.connection.clone()), ("execute", warehouse.adapter.getattr("execute")?), @@ -220,7 +220,7 @@ fn view<'py>( )); pairs.push(("claimed_by", optional(py, claimed_by.as_deref())?)); } - record(py, "JanitorOldNameView", &pairs) + record(py, ExecutorHost::JanitorOldNameView, &pairs) } /// Each project model's destination mapped to the model's name. @@ -272,7 +272,7 @@ pub(crate) fn plan_old_name_views<'py>( } if histories.is_empty() && early_drops.is_empty() { return Ok(OldNamePlanning { - planning: record(py, "JanitorOldNameViewPlanning", &[])?, + planning: record(py, ExecutorHost::JanitorOldNameViewPlanning, &[])?, keys: HashSet::new(), }); } @@ -284,8 +284,7 @@ pub(crate) fn plan_old_name_views<'py>( requested.push((name.clone(), false)); } } - let is_compatibility_view: Bound<'py, PyAny> = - imported(py, IS_COMPATIBILITY_VIEW.0, IS_COMPATIBILITY_VIEW.1)?; + let is_compatibility_view: Bound<'py, PyAny> = host::entry(py, IS_COMPATIBILITY_VIEW)?; let (mut live, mut drops, mut missing) = (Vec::new(), Vec::new(), Vec::new()); let mut keys: HashSet = HashSet::new(); for history in &histories { @@ -334,7 +333,7 @@ pub(crate) fn plan_old_name_views<'py>( .collect(); let planning: Bound<'py, PyAny> = record( py, - "JanitorOldNameViewPlanning", + ExecutorHost::JanitorOldNameViewPlanning, &[ ("live", tuple(py, live)?), ("drops", tuple(py, drops)?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_phases.rs b/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_phases.rs index a87458722b..1838d1a276 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_phases.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_phases.rs @@ -7,16 +7,16 @@ use pyo3::types::{PyBool, PyDict, PyDictMethods}; use crate::bindings::_helpers::build_progress::transient_status::status_reporter; use crate::bindings::_helpers::command_runtime::invocation::resolve_adapter_connection_context; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, within}; +use crate::bindings::_helpers::compile_driver::python_calls::{call_with, within}; use crate::bindings::_helpers::janitor::janitor_facts::Warehouse; use crate::bindings::_helpers::janitor::janitor_planning::{ JanitorSettings, RelationScope, build_plan, }; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const LIFECYCLE_MODULE: &str = "sqlbuild.runtime.observability.classes.operation_lifecycle"; -const JANITOR_EXECUTE_MODULE: &str = "sqlbuild.executor.janitor.main.execute"; -const ERRORS_MODULE: &str = "sqlbuild._native"; const UNKNOWN_VIEW_CODE: &str = "C503"; const UNKNOWN_VIEW_HELP: &str = "Run sqb janitor without --drop-old-name-view to list compatibility views."; @@ -48,7 +48,7 @@ fn lifecycle<'py>(py: Python<'py>, (kind, name): (&str, &str)) -> PyResult = PyDict::new(py); options.set_item("operation_kind", kind)?; options.set_item("operation_name", name)?; - imported(py, LIFECYCLE_MODULE, "OperationLifecycle")?.call((), Some(&options)) + host::entry(py, ExecutorHost::OperationLifecycle)?.call((), Some(&options)) } /// Compile for the janitor or `--as` target and resolve the active target connection. @@ -67,7 +67,7 @@ pub(crate) fn compile_janitor_project<'py>( )?; let started: Instant = Instant::now(); let status: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "PlanningProgressReporter")?, + ®istry::native(py, NativeAttr::PlanningProgressReporter)?, &[ ("stream", stdout(py)?), ( @@ -88,7 +88,7 @@ pub(crate) fn compile_janitor_project<'py>( within(&lifecycle(py, ("project", "project_compile"))?, |_| { status.call_method1("on_progress", ("Compiling project...",))?; let project: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "build_compiled_project")?, + ®istry::native(py, NativeAttr::BuildCompiledProject)?, &[ ( "discovered_inputs", @@ -131,7 +131,7 @@ pub(crate) fn connect_janitor_warehouse<'py>( ) -> PyResult> { let py: Python<'py> = invocation.py(); let progress: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "ConnectionProgressReporter")?, + ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, &[ ("adapter_name", compiled.adapter_name.clone()), ("stream", stdout(py)?), @@ -187,7 +187,7 @@ fn unknown_view_error(py: Python<'_>, requests: &Bound<'_, PyAny>) -> PyResult

= PyDict::new(py); options.set_item("code", UNKNOWN_VIEW_CODE)?; options.set_item("help", UNKNOWN_VIEW_HELP)?; - let error: Bound<'_, PyAny> = imported(py, ERRORS_MODULE, "CliUserError")?.call( + let error: Bound<'_, PyAny> = registry::error(py, ErrorId::CliUser)?.call( (format!( "no compatibility view is recorded at {}", names.join(", ") @@ -277,11 +277,7 @@ pub(crate) fn execute_janitor_cleanup<'py>( plan: &Bound<'py, PyAny>, ) -> PyResult> { call_with( - &imported( - compiled.adapter.py(), - JANITOR_EXECUTE_MODULE, - "execute_janitor_plan", - )?, + &host::entry(compiled.adapter.py(), ExecutorHost::ExecuteJanitorPlan)?, &[ ("plan", plan.clone()), ("adapter", compiled.adapter.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_planning.rs b/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_planning.rs index b16318279e..7ce8973aa3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_planning.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_planning.rs @@ -10,7 +10,7 @@ use sqlbuild_analysis::janitor_planning::main::build_archive_name::build_archive use sqlbuild_analysis::janitor_planning::main::query_diff_artifact_name::query_diff_artifact_name; use sqlbuild_analysis::janitor_planning::main::query_diff_artifact_parts::query_diff_artifact_parts; -use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported, within}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, within}; use crate::bindings::_helpers::janitor::janitor_classification::{ Candidate, Policy, Protection, SchemaClassification, classify_target_schemas, plan_archives, }; @@ -25,12 +25,11 @@ use crate::bindings::_helpers::janitor::janitor_keys::{ use crate::bindings::_helpers::janitor::janitor_old_names::{ OldNamePlanning, pending_migration_origins, plan_old_name_views, }; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::relation_naming::fitting::shared_error; -const LIFECYCLE: (&str, &str) = ( - "sqlbuild.runtime.observability.classes.operation_lifecycle", - "OperationLifecycle", -); +const LIFECYCLE: ExecutorHost = ExecutorHost::OperationLifecycle; /// Settings of one janitor plan. pub(crate) struct JanitorSettings { @@ -128,7 +127,7 @@ fn artifact_only_plan<'py>( let py: Python<'py> = warehouse.py(); record( py, - "JanitorPlan", + ExecutorHost::JanitorPlan, &[ ("target_name", project.getattr("effective_target_name")?), ("retention_days", integer(py, settings.retention_days)?), @@ -191,7 +190,7 @@ pub(crate) fn build_plan<'py>( &now, ); } - let lifecycle: Bound<'py, PyAny> = imported(py, LIFECYCLE.0, LIFECYCLE.1)?; + let lifecycle: Bound<'py, PyAny> = host::entry(py, LIFECYCLE)?; let options: Bound<'py, PyDict> = PyDict::new(py); options.set_item("operation_kind", "janitor")?; options.set_item("operation_name", "janitor_warehouse_inspection")?; @@ -265,7 +264,7 @@ pub(crate) fn build_plan<'py>( ); record( py, - "JanitorPlan", + ExecutorHost::JanitorPlan, &[ ("target_name", project.getattr("effective_target_name")?), ("retention_days", integer(py, settings.retention_days)?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/lifecycle_events/payloads.rs b/crates/sqlbuild-python/src/bindings/_helpers/lifecycle_events/payloads.rs index 17c0d9c764..73b43f104c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/lifecycle_events/payloads.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/lifecycle_events/payloads.rs @@ -5,12 +5,12 @@ use pyo3::types::{PyBool, PyDict, PyDictMethods, PyFloat, PyInt, PyList, PyStrin use sqlbuild_core::lifecycle_events::models::PayloadValue; use crate::bindings::_helpers::compile_driver::python_calls::imported; - -const EXCEPTIONS_MODULE: &str = "sqlbuild.runtime.observability.exceptions"; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; /// `ObservabilityValidationError(message)`. pub(crate) fn validation_error(py: Python<'_>, message: &str) -> PyErr { - match imported(py, EXCEPTIONS_MODULE, "ObservabilityValidationError") + match host::entry(py, ExecutorHost::ObservabilityValidationError) .and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_cache.rs b/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_cache.rs index 24da1a8fc2..1e08cd4c4f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_cache.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_cache.rs @@ -4,6 +4,8 @@ use std::collections::{HashMap, HashSet}; use std::hash::Hash; use std::path::{Path, PathBuf}; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyModule, PyResult, Python}; use pyo3::types::{PyBytes, PyBytesMethods, PyDict, PyDictMethods, PyList, PyTuple}; use sqlbuild_analysis::lineage_command::models::{LineageIndex, LineageKey, LineageNode}; @@ -138,8 +140,7 @@ pub(crate) fn lineage_fingerprint( prefix.extend(sqlbuild_version(py)?.as_bytes()); prefix.extend(encoded.downcast::()?.as_bytes()); let uncacheable: bool = py.version_info() < FIRST_PYTHON_SKIPPING_INTERRUPTED_LISTINGS; - py.import("sqlbuild._native")? - .getattr("relation_lineage_fingerprint")? + registry::native(py, NativeAttr::RelationLineageFingerprint)? .call1((project_dir, PyBytes::new(py, &prefix), uncacheable))? .extract() } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_command.rs b/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_command.rs index bb75f84d8b..ccbb57ac9f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_command.rs @@ -38,19 +38,9 @@ use crate::bindings::_helpers::lineage::lineage_cache::{ }; use crate::bindings::_helpers::lineage::lineage_index::{graph_index, index_views, lineage_keys}; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ERRORS_MODULE: &str = "sqlbuild._native"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; -const REJECT_UNIT_TESTS_MODULE: &str = "sqlbuild._native"; -const GRAPH_SELECTION_MODULE: &str = "sqlbuild._native"; -const SELECTION_MODULE: &str = "sqlbuild._native"; -const SELECTOR_TOKENS_MODULE: &str = "sqlbuild._native"; -const SELECTOR_PARSE_MODULE: &str = "sqlbuild._native"; -const SELECTOR_MATCH_MODULE: &str = "sqlbuild._native"; -const PLANNER_EXCEPTIONS: &str = "sqlbuild._native"; const UNKNOWN_SELECTOR_ERROR_CODE: &str = "S007"; const RICH_MODE: &str = "rich"; const ANALYSIS_HELP: &str = "enable SQL analysis or install SQLBuild with Polyglot support"; @@ -94,17 +84,13 @@ fn keywords<'py>( Ok(dict) } -fn imported<'py>(py: Python<'py>, module: &str, name: &str) -> PyResult> { - py.import(module)?.getattr(name) -} - /// `CliUserError(message, code=..., help=...)`. fn user_error(py: Python<'_>, error: LineageError) -> PyErr { let raised = (|| { let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("code", error.code)?; options.set_item("help", error.help)?; - imported(py, ERRORS_MODULE, "CliUserError")?.call((error.message,), Some(&options)) + registry::error(py, ErrorId::CliUser)?.call((error.message,), Some(&options)) })(); match raised { Ok(error) => PyErr::from_value(error), @@ -149,7 +135,7 @@ fn discover<'py>( options.set_item("project_dir", project_dir)?; options.set_item("sql_analysis_enabled_override", analysis_override)?; options.set_item("extract_output_column_locations", false)?; - imported(py, NATIVE_MODULE, "discover_project_inputs")?.call((), Some(&options)) + registry::native(py, NativeAttr::DiscoverProjectInputs)?.call((), Some(&options)) } fn reject_unit_tests( @@ -165,8 +151,7 @@ fn reject_unit_tests( ("discovered_inputs", discovered), ], )?; - imported(py, REJECT_UNIT_TESTS_MODULE, "reject_unit_test_selectors")? - .call((), Some(&options))?; + registry::native(py, NativeAttr::RejectUnitTestSelectors)?.call((), Some(&options))?; Ok(()) } @@ -191,7 +176,7 @@ fn prepare_source<'py>( discover(py, project_dir, if compiled { None } else { Some(false) })?; reject_unit_tests(py, request, &discovered)?; let adapter_name: Bound<'py, PyAny> = - imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?.call( + registry::native(py, NativeAttr::ResolveEffectiveAdapterName)?.call( (), Some(&keywords( py, @@ -214,7 +199,7 @@ fn prepare_source<'py>( options.set_item("no_sql_validation", no_validation)?; options.set_item("skip_column_inference", !compiled)?; let graph: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "build_project_graph")?.call((), Some(&options))?; + registry::native(py, NativeAttr::BuildProjectGraph)?.call((), Some(&options))?; let index: LineageIndex = graph_index(&graph)?; if let Some(fingerprint) = &fingerprint { write_lineage_cache(py, &directory, fingerprint, &index)?; @@ -281,8 +266,7 @@ fn semantic_uses( ], )?; let keys: Vec = lineage_keys( - &imported(py, GRAPH_SELECTION_MODULE, "select_project_graph")? - .call((), Some(&options))?, + ®istry::native(py, NativeAttr::SelectProjectGraph)?.call((), Some(&options))?, )?; keys.into_iter() .filter(|key| key.resource_type == MODEL_RESOURCE_TYPE) @@ -299,7 +283,7 @@ fn semantic_uses( .collect() }; let uses: Bound<'_, PyAny> = - imported(py, NATIVE_MODULE, "build_direct_semantic_uses")?.call1(( + registry::native(py, NativeAttr::BuildDirectSemanticUses)?.call1(( graph.getattr("project")?, adapter.call_method0("sql_analysis_dialect")?, PyFrozenSet::new(py, model_names)?, @@ -389,7 +373,7 @@ fn column_lineage<'py>( py, (&message, rich && count >= RICH_STATUS_MODEL_THRESHOLD), || { - imported(py, NATIVE_MODULE, "build_project_column_lineage")?.call1(( + registry::native(py, NativeAttr::BuildProjectColumnLineage)?.call1(( graph.getattr("project")?, py.None(), mode, @@ -484,9 +468,9 @@ fn selector_anchors( select: &[String], require_clear_anchors: bool, ) -> PyResult { - let parse: Bound<'_, PyAny> = imported(py, SELECTOR_PARSE_MODULE, "parse_project_selector")?; - let matcher: Bound<'_, PyAny> = imported(py, SELECTOR_MATCH_MODULE, "match_project_selector")?; - let path_class: Bound<'_, PyAny> = imported(py, NATIVE_RECORDS_MODULE, "PathSelector")?; + let parse: Bound<'_, PyAny> = registry::native(py, NativeAttr::ParseProjectSelector)?; + let matcher: Bound<'_, PyAny> = registry::native(py, NativeAttr::MatchProjectSelector)?; + let path_class: Bound<'_, PyAny> = registry::class(py, RecordId::PathSelector)?; let names: HashMap<&str, &LineageKey> = name_lookup(index); let named = |name: String| -> PyResult { names @@ -564,7 +548,7 @@ fn selector_rendering( options.set_item("select", &select)?; options.set_item("exclude", &exclude)?; let selected: Vec = lineage_keys( - &imported(py, SELECTION_MODULE, "resolve_project_selectors")?.call((), Some(&options))?, + ®istry::native(py, NativeAttr::ResolveProjectSelectors)?.call((), Some(&options))?, )?; let index: &LineageIndex = source.index(); let direction: Option = request.getattr("direction")?.extract()?; @@ -573,12 +557,8 @@ fn selector_rendering( let options: Bound<'_, PyDict> = view_options(&views, &GRAPH_VIEW_NAMES)?; options.set_item("selectors", &exclude)?; let excluded: Vec = lineage_keys( - &imported( - py, - SELECTOR_TOKENS_MODULE, - "resolve_project_selector_tokens", - )? - .call((), Some(&options))?, + ®istry::native(py, NativeAttr::ResolveProjectSelectorTokens)? + .call((), Some(&options))?, )?; select_directional_lineage(index, (&selected, &excluded), (&direction, depth)) } else { @@ -597,8 +577,7 @@ fn selector_failure( error: PyErr, ) -> PyErr { let check = || -> PyResult<()> { - let planner_error: Bound<'_, PyAny> = - imported(py, PLANNER_EXCEPTIONS, "PlannerInputError")?; + let planner_error: Bound<'_, PyAny> = registry::error(py, ErrorId::PlannerInput)?; if !matches!(source, LineageSource::Cached(_)) || !error.is_instance(py, &planner_error) || error.value(py).getattr("code")?.extract::()? != UNKNOWN_SELECTOR_ERROR_CODE diff --git a/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_index.rs b/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_index.rs index 5edcd89fd8..42a2a7e533 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_index.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_index.rs @@ -2,13 +2,12 @@ use std::collections::HashMap; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyFrozenSet, PyTuple}; use sqlbuild_analysis::lineage_command::models::{LineageIndex, LineageKey, LineageNode}; -const COMPILE_MODELS: &str = "sqlbuild._native"; -const GRAPH_MODELS: &str = "sqlbuild._native"; - /// A Python `CompiledObjectKey` as a lineage key. pub(crate) fn lineage_key(key: &Bound<'_, PyAny>) -> PyResult { Ok(LineageKey { @@ -31,7 +30,7 @@ pub(crate) struct PythonKeys<'py> { impl<'py> PythonKeys<'py> { pub(crate) fn new(py: Python<'py>) -> PyResult { Ok(Self { - class: py.import(COMPILE_MODELS)?.getattr("CompiledObjectKey")?, + class: registry::class(py, RecordId::CompiledObjectKey)?, made: HashMap::new(), }) } @@ -212,7 +211,5 @@ pub(crate) fn index_views<'py>( options.set_item("tag_index", tags)?; options.set_item("path_index", paths)?; options.set_item("all_keys", names)?; - py.import(GRAPH_MODELS)? - .getattr("LineageGraphViews")? - .call((), Some(&options)) + registry::class(py, RecordId::LineageGraphViews)?.call((), Some(&options)) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_objects.rs b/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_objects.rs index a55df86374..59832a8c6a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_objects.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_objects.rs @@ -1,16 +1,16 @@ //! The Python lineage models and enums native lineage builds. -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyResult, Python}; use pyo3::types::{PyDict, PyTuple}; use sqlbuild_analysis::lineage::models::{LineageColumn, LineageSource}; -const LINEAGE_MODELS_MODULE: &str = "sqlbuild._native"; const LINEAGE_TYPES_MODULE: &str = "sqlbuild.compiler.lineage.types"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; const LOGGING_MODULE: &str = "logging"; -const DEBUG_EVENT_MODULE: &str = "sqlbuild.diagnostics.main.log_debug_event"; const LINEAGE_LOGGER: &str = "sqlbuild.lineage"; /// The lineage model classes and enums, imported once per call. @@ -35,26 +35,24 @@ pub(crate) struct LineageClasses<'py> { impl<'py> LineageClasses<'py> { pub(crate) fn new(py: Python<'py>) -> PyResult { - let models: Bound<'py, PyModule> = py.import(LINEAGE_MODELS_MODULE)?; let types: Bound<'py, PyModule> = py.import(LINEAGE_TYPES_MODULE)?; - let compile_models: Bound<'py, PyModule> = py.import(COMPILE_MODELS_MODULE)?; let resource_type: Bound<'py, PyAny> = py .import(COMPILE_TYPES_MODULE)? .getattr("CompiledResourceType")?; let nullability: Bound<'py, PyAny> = types.getattr("InferredNullability")?; Ok(Self { - qualified_column: models.getattr("QualifiedLineageColumn")?, - lineage_source: models.getattr("ColumnLineageSource")?, - lineage_edge: models.getattr("ColumnLineageEdge")?, - column_lineage: models.getattr("ColumnLineage")?, - model_lineage: models.getattr("ModelColumnLineage")?, - semantic_use: models.getattr("DirectSemanticColumnUse")?, - column_fact: shape_class(compile_models.as_any(), "CompiledLineageColumnFact")?, - source_fact: compile_models.getattr("CompiledLineageSourceFact")?, - compact_facts: compile_models.getattr("CompactLineageFacts")?, - transform_kind: shape_class(types.as_any(), "ColumnTransformKind")?, - confidence: shape_class(types.as_any(), "ColumnLineageConfidence")?, - analysis_dialect: shape_class(types.as_any(), "PolyglotAnalysisDialect")?, + qualified_column: registry::class(py, RecordId::QualifiedLineageColumn)?, + lineage_source: registry::class(py, RecordId::ColumnLineageSource)?, + lineage_edge: registry::class(py, RecordId::ColumnLineageEdge)?, + column_lineage: registry::class(py, RecordId::ColumnLineage)?, + model_lineage: registry::class(py, RecordId::ModelColumnLineage)?, + semantic_use: registry::class(py, RecordId::DirectSemanticColumnUse)?, + column_fact: registry::class(py, RecordId::CompiledLineageColumnFact)?, + source_fact: registry::class(py, RecordId::CompiledLineageSourceFact)?, + compact_facts: registry::class(py, RecordId::CompactLineageFacts)?, + transform_kind: registry::class(types.py(), RecordId::ColumnTransformKind)?, + confidence: registry::class(types.py(), RecordId::ColumnLineageConfidence)?, + analysis_dialect: registry::class(types.py(), RecordId::PolyglotAnalysisDialect)?, model_resource: resource_type.getattr("MODEL")?, unknown_nullability: nullability.getattr("UNKNOWN")?, nullability, @@ -177,9 +175,6 @@ pub(crate) fn log_lineage_event( fields.set_item("message", message)?; fields.set_item("sqlbuild_model", model_name)?; fields.set_item("sqlbuild_error", error)?; - let _ = py - .import(DEBUG_EVENT_MODULE)? - .getattr("log_debug_event")? - .call((), Some(&fields))?; + let _ = host::entry(py, ExecutorHost::LogDebugEvent)?.call((), Some(&fields))?; Ok(()) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/lineage/project_lineage.rs b/crates/sqlbuild-python/src/bindings/_helpers/lineage/project_lineage.rs index 470d1f1b80..8fdcbdc561 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/lineage/project_lineage.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/lineage/project_lineage.rs @@ -22,10 +22,11 @@ use crate::bindings::_helpers::lineage::lineage_objects::{LineageClasses, log_li use crate::bindings::_helpers::lineage::project_schema::{ fast_schema, inferred_names, rich_schema, }; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; use crate::bindings::models::ProjectCatalog; use crate::bindings::types::CompilerDetach; -const EXCEPTIONS_MODULE: &str = "sqlbuild._native"; const FAST_MODE: &str = "fast"; const RICH_MODE: &str = "rich"; const GENERIC_DIALECT: &str = "generic"; @@ -266,9 +267,7 @@ fn output_columns(classes: &LineageClasses<'_>, facts: &Bound<'_, PyAny>) -> PyR } fn unknown_dialect_error(py: Python<'_>, dialect: &str) -> PyErr { - match py - .import(EXCEPTIONS_MODULE) - .and_then(|module| module.getattr("UnknownLineageDialectError")) + match registry::error(py, ErrorId::UnknownLineageDialect) .and_then(|class| class.call1((format!("Unknown dialect: {dialect}"),))) { Ok(error) => PyErr::from_value(error), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/load/dlt_destination.rs b/crates/sqlbuild-python/src/bindings/_helpers/load/dlt_destination.rs index 47c179dd7c..b76e0c358d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/load/dlt_destination.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/load/dlt_destination.rs @@ -14,11 +14,10 @@ use sqlbuild_core::loader_connection::models::LoaderConnection; use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::load::ingestr_command::connection; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::third_party::ThirdPartyHost; -const DLT_ERROR: (&str, &str) = ( - "sqlbuild.integrations.dlt.exceptions", - "DltIntegrationError", -); +const DLT_ERROR: ThirdPartyHost = ThirdPartyHost::DltIntegrationError; /// `(factory, arguments, options, dataset_name)` of one dlt destination. type DestinationCall<'py> = ( @@ -30,7 +29,7 @@ type DestinationCall<'py> = ( /// A `DltIntegrationError`, or the failure to construct one. fn dlt_error(py: Python<'_>, message: String) -> PyErr { - imported(py, DLT_ERROR.0, DLT_ERROR.1) + host::entry(py, DLT_ERROR) .and_then(|class| class.call1((message,))) .map_or_else(|error| error, PyErr::from_value) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/load/ingestr_command.rs b/crates/sqlbuild-python/src/bindings/_helpers/load/ingestr_command.rs index 11a990d48a..0a81034f77 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/load/ingestr_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/load/ingestr_command.rs @@ -16,21 +16,13 @@ use sqlbuild_core::loader_connection::models::LoaderConnection; use sqlbuild_model_config::templates::models::TemplateOptions; use crate::bindings::_helpers::boundary::keyword_request::Request; -use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::model_config::template_data::expanded_template_data; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::third_party::ThirdPartyHost; -const INGESTR_ERROR: (&str, &str) = ( - "sqlbuild.integrations.ingestr.exceptions", - "IngestrIntegrationError", -); -const INGESTR_SOURCE_CONFIG: (&str, &str) = ( - "sqlbuild.integrations.ingestr.models", - "IngestrSourceConfig", -); -const INGESTR_KIND: (&str, &str) = ( - "sqlbuild.integrations.ingestr.constants", - "INGESTR_INTEGRATION_KIND", -); +const INGESTR_ERROR: ThirdPartyHost = ThirdPartyHost::IngestrIntegrationError; +const INGESTR_SOURCE_CONFIG: ThirdPartyHost = ThirdPartyHost::IngestrSourceConfig; +const INGESTR_KIND: ThirdPartyHost = ThirdPartyHost::IngestrIntegrationKind; const TEMPLATE_LABEL: &str = "ingestr source config"; const TEMPLATE_FIELDS: [&str; 7] = [ "source_uri", @@ -45,7 +37,7 @@ const LIST_FIELDS: [&str; 2] = ["primary_key", "extra_args"]; /// An `IngestrIntegrationError`, or the failure to construct one. fn ingestr_error(py: Python<'_>, message: String) -> PyErr { - imported(py, INGESTR_ERROR.0, INGESTR_ERROR.1) + host::entry(py, INGESTR_ERROR) .and_then(|class| class.call1((message,))) .map_or_else(|error| error, PyErr::from_value) } @@ -55,22 +47,14 @@ fn ingestr_config<'py>(source_entry: &Bound<'py, PyAny>) -> PyResult = source_entry.py(); let name: String = source_entry.getattr("name")?.str()?.extract()?; let loader: Bound<'py, PyAny> = source_entry.getattr("integration_loader")?; - if loader.is_none() - || !loader - .getattr("kind")? - .eq(imported(py, INGESTR_KIND.0, INGESTR_KIND.1)?)? - { + if loader.is_none() || !loader.getattr("kind")?.eq(host::entry(py, INGESTR_KIND)?)? { return Err(ingestr_error( py, format!("Source '{name}' does not define ingestr config"), )); } let config: Bound<'py, PyAny> = loader.getattr("config")?; - if !config.is_instance(&imported( - py, - INGESTR_SOURCE_CONFIG.0, - INGESTR_SOURCE_CONFIG.1, - )?)? { + if !config.is_instance(&host::entry(py, INGESTR_SOURCE_CONFIG)?)? { return Err(ingestr_error( py, format!("Source '{name}' defines invalid ingestr config"), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/load/load_command.rs b/crates/sqlbuild-python/src/bindings/_helpers/load/load_command.rs index 5cffcea16c..8b3b0fb10c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/load/load_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/load/load_command.rs @@ -17,7 +17,7 @@ use sqlbuild_core::load_command::main::render_load_ready::render_load_ready; use sqlbuild_core::load_command::models::{LoadCounts, LoadResultText}; use crate::bindings::_helpers::command_runtime::adapters::resolve_cli_adapter; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::cursor::overrides::{cursor_integer, cursor_timestamp}; use crate::bindings::_helpers::load::load_progress::{ LoadCallbackRelay, LoadProgressReporter, load_result_text, @@ -25,23 +25,14 @@ use crate::bindings::_helpers::load::load_progress::{ use crate::bindings::_helpers::load::load_selection::{ LoadProject, LoaderDefaults, loader_defaults, }; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ERRORS_MODULE: &str = "sqlbuild._native"; -const REJECT_UNIT_TESTS_MODULE: &str = "sqlbuild._native"; -const EFFECTIVE_TARGET_MODULE: &str = "sqlbuild._native"; -const EFFECTIVE_RUNTIME_MODULE: &str = "sqlbuild._native"; -const EFFECTIVE_SETTINGS_MODULE: &str = "sqlbuild._native"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; -const RELATION_LOOKUP_MODULE: &str = "sqlbuild.adapter.relations.main.relation_lookup"; -const CURSOR_OVERRIDES_MODULE: &str = "sqlbuild._native"; -const PLANNER_MODELS: &str = "sqlbuild._native"; -const PROVIDER_SESSION_MODULE: &str = "sqlbuild.provider.main.session"; -const PYTHON_NODE_MODELS: &str = "sqlbuild.python_nodes.models"; -const PYTHON_NODE_TYPES: &str = "sqlbuild.python_nodes.types"; -const LOAD_MODELS: &str = "sqlbuild.executor.load.models"; -const LOAD_RUN_MODULE: &str = "sqlbuild.executor.load.main.run"; const LOADER_NODE_KEY: &str = "sqlbuild_loader_node"; const EXECUTION_COMMAND: &str = "sqb load"; @@ -84,13 +75,13 @@ fn invocation<'py>(py: Python<'py>, request: &Bound<'py, PyAny>) -> PyResult py.import("pathlib")?.getattr("Path")?.call_method0("cwd")?, }; let discovered: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[("project_dir", project_dir.clone())], )?; let select: Bound<'py, PyAny> = request.getattr("select")?; let exclude: Bound<'py, PyAny> = request.getattr("exclude")?; call_with( - &imported(py, REJECT_UNIT_TESTS_MODULE, "reject_unit_test_selectors")?, + ®istry::native(py, NativeAttr::RejectUnitTestSelectors)?, &[ ("select", select.clone()), ("exclude", exclude.clone()), @@ -99,7 +90,7 @@ fn invocation<'py>(py: Python<'py>, request: &Bound<'py, PyAny>) -> PyResult = request.getattr("selected_target")?; let target_config: Bound<'py, PyAny> = call_with( - &imported(py, EFFECTIVE_TARGET_MODULE, "build_effective_target_config")?, + ®istry::native(py, NativeAttr::BuildEffectiveTargetConfig)?, &[ ("discovered_inputs", discovered.clone()), ("selected_target", selected_target.clone()), @@ -141,7 +132,7 @@ fn invocation<'py>(py: Python<'py>, request: &Bound<'py, PyAny>) -> PyResult, message: String) -> PyErr { - match imported(py, ERRORS_MODULE, "CliUserError").and_then(|class| class.call1((message,))) { + match registry::error(py, ErrorId::CliUser).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } @@ -187,7 +178,7 @@ fn check_reference_targets( }) .collect::>()?; let lookup: Bound<'_, PyAny> = call_with( - &imported(py, RELATION_LOOKUP_MODULE, "build_relation_lookup")?, + &host::entry(py, AdapterHost::BuildRelationLookup)?, &[ ("adapter", adapter.clone()), ("connection", connection.clone()), @@ -253,10 +244,10 @@ fn project_relations<'py>( sources.set_item(source.getattr("name")?, source)?; } } - let reference_class: Bound<'py, PyAny> = imported(py, PYTHON_NODE_MODELS, "SqlResourceRef")?; + let reference_class: Bound<'py, PyAny> = host::entry(py, UserCodeHost::SqlResourceRef)?; let source_kind: Bound<'py, PyAny> = - imported(py, PYTHON_NODE_TYPES, "SqlResourceRefKind")?.getattr("SOURCE")?; - let render: Bound<'py, PyAny> = imported(py, NATIVE_MODULE, "render_source_relation")?; + host::entry(py, UserCodeHost::SqlResourceRefKind)?.getattr("SOURCE")?; + let render: Bound<'py, PyAny> = registry::native(py, NativeAttr::RenderSourceRelation)?; let relations: Bound<'py, PyDict> = PyDict::new(py); for (name, source) in sources.iter() { let key: Bound<'py, PyAny> = call_with( @@ -277,7 +268,7 @@ fn prepare<'py>( let py: Python<'py> = request.py(); let discovered: &Bound<'py, PyAny> = &invocation.discovered; let adapter_name: Bound<'py, PyAny> = call_with( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), @@ -285,7 +276,7 @@ fn prepare<'py>( )?; let adapter: Bound<'py, PyAny> = resolve_cli_adapter(&adapter_name, &invocation.project_dir)?; let connection_config: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "resolve_project_connection_config")?, + ®istry::native(py, NativeAttr::ResolveProjectConnectionConfig)?, &[ ("discovered_inputs", discovered.clone()), ("project_dir", invocation.project_dir.clone()), @@ -295,11 +286,7 @@ fn prepare<'py>( )?; check_reference_targets(invocation, (&adapter, &connection_config))?; let runtime: (Bound<'py, PyAny>, Bound<'py, PyAny>, Bound<'py, PyAny>) = call_with( - &imported( - py, - EFFECTIVE_RUNTIME_MODULE, - "build_effective_runtime_config", - )?, + ®istry::native(py, NativeAttr::BuildEffectiveRuntimeConfig)?, &[ ("discovered_inputs", discovered.clone()), ("selected_target", request.getattr("selected_target")?), @@ -310,11 +297,7 @@ fn prepare<'py>( let requested: Bound<'py, PyAny> = request.getattr("concurrency")?; let concurrency: i64 = if requested.is_none() { call_with( - &imported( - py, - EFFECTIVE_SETTINGS_MODULE, - "build_effective_settings_config", - )?, + ®istry::native(py, NativeAttr::BuildEffectiveSettingsConfig)?, &[("discovered_inputs", discovered.clone())], )? .getattr("concurrency")? @@ -323,16 +306,16 @@ fn prepare<'py>( requested.extract()? }; let typed: Bound<'py, PyAny> = call_with( - &imported(py, CURSOR_OVERRIDES_MODULE, "type_cursor_overrides")?, + ®istry::native(py, NativeAttr::TypeCursorOverrides)?, &[("cursor_overrides", request.getattr("cursor_overrides")?)], )?; let cursor_overrides: Bound<'py, PyAny> = if typed.is_truthy()? { typed } else { - imported(py, PLANNER_MODELS, "CursorOverrides")?.call0()? + registry::class(py, RecordId::CursorOverrides)?.call0()? }; let provider_session: Bound<'py, PyAny> = call_with( - &imported(py, PROVIDER_SESSION_MODULE, "build_provider_session")?, + &host::entry(py, UserCodeHost::BuildProviderSession)?, &[("discovered_providers", discovered.getattr("providers")?)], )?; Ok(Preparation { @@ -357,7 +340,7 @@ fn runtime_params<'py>( let (target_name, effective_vars, run_id) = &preparation.runtime; let overrides: &Bound<'py, PyAny> = &preparation.cursor_overrides; call_with( - &imported(py, LOAD_MODELS, "LoadRuntimeParams")?, + &host::entry(py, ExecutorHost::LoadRuntimeParams)?, &[ ("run_id", run_id.clone()), ( @@ -409,13 +392,13 @@ fn execute<'py>( let py: Python<'py> = request.py(); let started: Instant = Instant::now(); let event_writer: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "ExecutionEventWriter")?.call0()?; + registry::native(py, NativeAttr::ExecutionEventWriter)?.call0()?; let order: Bound<'py, PyDict> = PyDict::new(py); for (index, source) in invocation.selected.iter().enumerate() { order.set_item(source.getattr("name")?, index + 1)?; } let reporter: Bound<'py, LoadProgressReporter> = - imported(py, NATIVE_MODULE, "LoadProgressReporter")? + registry::native(py, NativeAttr::LoadProgressReporter)? .call( (), Some(&{ @@ -429,7 +412,7 @@ fn execute<'py>( )? .downcast_into()?; let connection: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "ConnectionProgressReporter")?, + ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, &[ ("adapter_name", text(py, &preparation.adapter_name)), ("stream", invocation.stream.clone()), @@ -455,7 +438,7 @@ fn execute<'py>( )?; let executed: PyResult>> = (|| { let callbacks: Bound<'py, PyAny> = call_with( - &imported(py, LOAD_MODELS, "LoadCallbacks")?, + &host::entry(py, ExecutorHost::LoadCallbacks)?, &[ ("on_load_start", reporter.getattr("on_start")?), ("on_load_progress", reporter.getattr("on_progress")?), @@ -472,7 +455,7 @@ fn execute<'py>( ], )?; call_with( - &imported(py, LOAD_RUN_MODULE, "run_load_pipeline")?, + &host::entry(py, ExecutorHost::RunLoadPipeline)?, &[ ( "sources", @@ -508,11 +491,11 @@ fn execute<'py>( fn write_json(request: &Bound<'_, PyAny>, results: &[Bound<'_, PyAny>]) -> PyResult<()> { let py: Python<'_> = request.py(); let payload: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "format_load_execution_json")?, + ®istry::native(py, NativeAttr::FormatLoadExecutionJson)?, &[("results", PyTuple::new(py, results)?.into_any())], )?; call_with( - &imported(py, NATIVE_MODULE, "write_execution_json_output")?, + ®istry::native(py, NativeAttr::WriteExecutionJsonOutput)?, &[ ("payload", payload), ("json_output", request.getattr("json_output")?), @@ -566,7 +549,7 @@ fn run_load_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult options.set_item("target", py.None())?; options.set_item("concurrency", preparation.concurrency)?; options.set_item("use_color", invocation.style.use_color)?; - imported(py, NATIVE_MODULE, "write_execution_header")?.call((), Some(&options))?; + registry::native(py, NativeAttr::WriteExecutionHeader)?.call((), Some(&options))?; execute(request, &invocation, &preparation) })(); let closed: PyResult> = preparation.provider_session.call_method0("close"); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/load/load_progress.rs b/crates/sqlbuild-python/src/bindings/_helpers/load/load_progress.rs index 1ae444823e..703f54293a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/load/load_progress.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/load/load_progress.rs @@ -12,11 +12,12 @@ use sqlbuild_core::load_command::main::render_load_spinner_line::render_load_spi use sqlbuild_core::load_command::models::{LoadResultText, LoadSpinnerRow, LoadStatus}; use crate::bindings::_helpers::build_progress::terminal::start_spinner; -use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported, within}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, within}; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const LINES_MODULE: &str = "sqlbuild._native"; -const LIFECYCLE_TYPES: &str = "sqlbuild.adapter.contract.types"; const LOADER_NODE_KEY: &str = "sqlbuild_loader_node"; const SPINNER_TICK_SECONDS: f64 = 0.1; const SPINNER_JOIN_SECONDS: f64 = 0.2; @@ -35,7 +36,7 @@ fn optional_text(value: Bound<'_, PyAny>) -> PyResult> { pub(crate) fn load_result_text(result: &Bound<'_, PyAny>) -> PyResult { let py: Python<'_> = result.py(); let log_kind: Bound<'_, PyAny> = - imported(py, LIFECYCLE_TYPES, "LifeCycleEventKind")?.getattr("LOG")?; + host::entry(py, AdapterHost::LifeCycleEventKind)?.getattr("LOG")?; let mut log_messages: Vec = Vec::new(); for event in result.getattr("lifecycle_events")?.try_iter()? { let event: Bound<'_, PyAny> = event?; @@ -199,9 +200,9 @@ impl LoadProgressReporter { let source_order: HashMap = arguments.value("source_order")?.extract()?; let total_count: i64 = arguments.value("total_count")?.extract()?; let lines: Bound<'_, PyAny> = - imported(py, LINES_MODULE, "shared_transient_line_coordinator")?.call0()?; + registry::native(py, NativeAttr::SharedTransientLineCoordinator)?.call0()?; let projector: Bound<'_, PyAny> = - imported(py, NATIVE_MODULE, "current_native_progress_projector")?.call0()?; + registry::native(py, NativeAttr::CurrentNativeProgressProjector)?.call0()?; if !projector.is_none() { let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("ordinals", source_order.clone())?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/load/load_selection.rs b/crates/sqlbuild-python/src/bindings/_helpers/load/load_selection.rs index 9f46362752..e9ef6cf49d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/load/load_selection.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/load/load_selection.rs @@ -15,13 +15,11 @@ use sqlbuild_core::load_command::models::{LoadEntry, LoadGraph}; use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, }; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const ERRORS_MODULE: &str = "sqlbuild._native"; -const CONTRACT_MODELS: &str = "sqlbuild._native"; -const DESTINATION_MODULE: &str = "sqlbuild._native"; -const CONNECTION_CONFIG_MODULE: &str = "sqlbuild._native"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; -const ADAPTER_TYPES_MODULE: &str = "sqlbuild.adapter.contract.types"; const LOADER_NODE_KEY: &str = "sqlbuild_loader_node"; const LOADER_TABLE_PREFIX: &str = "__loader__"; const UNKNOWN_SELECTOR_CODE: &str = "C901"; @@ -125,7 +123,7 @@ impl<'py> LoadProject<'py> { destination }; let parts: Bound<'py, PyAny> = call_with( - &imported(py, DESTINATION_MODULE, "loader_destination_parts")?, + ®istry::native(py, NativeAttr::LoaderDestinationPartsFunction)?, &[ ("destination", destination), ("default_database", defaults.database.clone()), @@ -136,7 +134,7 @@ impl<'py> LoadProject<'py> { meta.set_item(LOADER_NODE_KEY, true)?; let name_value: Bound<'py, PyAny> = PyString::new(py, name).into_any(); call_with( - &imported(py, CONTRACT_MODELS, "SourceEntry")?, + ®istry::class(py, RecordId::SourceEntry)?, &[ ("name", name_value.clone()), ("database", parts.getattr("database")?), @@ -204,7 +202,7 @@ fn unknown_selector(py: Python<'_>, error: &UnknownLoadSelector) -> PyErr { let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("code", UNKNOWN_SELECTOR_CODE)?; options.set_item("help", UNKNOWN_SELECTOR_HELP)?; - imported(py, ERRORS_MODULE, "CliUserError")?.call( + registry::error(py, ErrorId::CliUser)?.call( (format!( "sqb load selector '{}' does not match any managed source or loader", error.name @@ -255,11 +253,7 @@ pub(crate) fn loader_defaults<'py>( ) -> PyResult> { let py: Python<'py> = discovered.py(); let connection: Bound<'py, PyDict> = call_with( - &imported( - py, - CONNECTION_CONFIG_MODULE, - "build_effective_connection_config", - )?, + ®istry::native(py, NativeAttr::BuildEffectiveConnectionConfig)?, &[ ("discovered_inputs", discovered.clone()), ("selected_target", selected_target.clone()), @@ -268,14 +262,14 @@ pub(crate) fn loader_defaults<'py>( )? .downcast_into()?; let adapter_name: Bound<'py, PyAny> = call_with( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), ], )?; let duckdb: Bound<'py, PyAny> = - imported(py, ADAPTER_TYPES_MODULE, "BuiltinAdapter")?.getattr("DUCKDB")?; + host::entry(py, AdapterHost::BuiltinAdapter)?.getattr("DUCKDB")?; let connection_database: Option> = if adapter_name.eq(duckdb)? { None } else { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/manifest/builder.rs b/crates/sqlbuild-python/src/bindings/_helpers/manifest/builder.rs index d78fbea497..4f88fb792e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/manifest/builder.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/manifest/builder.rs @@ -19,9 +19,11 @@ use crate::bindings::_helpers::graph::views::native_graph_from_edges; use crate::bindings::_helpers::manifest::rows::{ ProjectRows, plan_input, project_rows, python_hooks, }; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; -const FINGERPRINT_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; -const HOOK_IDENTITIES_MODULE: &str = "sqlbuild.compiler.python_nodes.main._hook_identities"; const DATETIME_MODULE: &str = "datetime"; /// A built manifest: its `manifest.json` bytes, or the error `json.dumps` raised encoding it. @@ -51,9 +53,7 @@ impl NativeManifest { fn python_error(error: ManifestError, py: Python<'_>) -> PyErr { match error { - ManifestError::QueryFingerprint(message) => py - .import(FINGERPRINT_EXCEPTIONS_MODULE) - .and_then(|module| module.getattr("QueryFingerprintError")) + ManifestError::QueryFingerprint(message) => registry::error(py, ErrorId::QueryFingerprint) .and_then(|class| class.call1((message,))) .map_or_else(|error| error, PyErr::from_value), ManifestError::MissingParameterType(name) => PyKeyError::new_err(name), @@ -89,9 +89,7 @@ pub(crate) fn build_manifest(options: Option<&Bound<'_, PyDict>>) -> PyResult = py - .import(HOOK_IDENTITIES_MODULE)? - .getattr("build_hook_identities")? + let hook_identities: Bound<'_, PyAny> = host::entry(py, UserCodeHost::BuildHookIdentities)? .call1((project.getattr("hook_functions")?,))?; compiler_guard(|| { let python_hooks = python_hooks(&project, &hook_identities)?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/manifest/rows.rs b/crates/sqlbuild-python/src/bindings/_helpers/manifest/rows.rs index d5c16fb94f..217ea3d27d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/manifest/rows.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/manifest/rows.rs @@ -13,8 +13,9 @@ use sqlbuild_analysis::manifest::models::{ use sqlbuild_analysis::python_values::models::PythonValue; use crate::bindings::_helpers::python_values::values::python_value; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const DISCOVERY_MODELS_MODULE: &str = "sqlbuild._native"; const DECIMAL_KIND: &str = "decimal"; const MACRO_KIND: &str = "macro"; const DEFAULT_MATERIALIZATION: &str = "view"; @@ -147,9 +148,8 @@ fn hooks(value: &Bound<'_, PyAny>) -> PyResult>> { if !(value.is_instance_of::() || value.is_instance_of::()) { return Ok(None); } - let module: Bound<'_, PyAny> = value.py().import(DISCOVERY_MODELS_MODULE)?.into_any(); - let sql_class: Bound<'_, PyAny> = module.getattr("SqlHookEntry")?; - let python_class: Bound<'_, PyAny> = module.getattr("PythonHookEntry")?; + let sql_class: Bound<'_, PyAny> = registry::class(value.py(), RecordId::SqlHookEntry)?; + let python_class: Bound<'_, PyAny> = registry::class(value.py(), RecordId::PythonHookEntry)?; value .try_iter()? .map(|entry| hook(&entry?, (&sql_class, &python_class))) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/microbatches/conversion.rs b/crates/sqlbuild-python/src/bindings/_helpers/microbatches/conversion.rs index dca974875b..e9181b2e41 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/microbatches/conversion.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/microbatches/conversion.rs @@ -8,18 +8,14 @@ use sqlbuild_cursor::algebra::main::cursor_sort_key::cursor_sort_key; use sqlbuild_cursor::algebra::models::{Scalar, SortKey}; use sqlbuild_state::microbatches::models::{BoundInterval, CursorBound}; -use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::cursor::conversion::CursorSession; use crate::bindings::_helpers::cursor::scalars::as_scalar; - -pub(crate) const MODELS_MODULE: &str = "sqlbuild.microbatches.models"; -pub(crate) const TYPES_MODULE: &str = "sqlbuild.microbatches.types"; -pub(crate) const CONSTANTS_MODULE: &str = "sqlbuild.microbatches.constants"; -const EXCEPTIONS_MODULE: &str = "sqlbuild.microbatches.exceptions"; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; /// `MicrobatchStateError(message)`. pub(crate) fn state_error(py: Python<'_>, message: &str) -> PyErr { - match imported(py, EXCEPTIONS_MODULE, "MicrobatchStateError") + match host::entry(py, ExecutorHost::MicrobatchStateError) .and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/microbatches/projection.rs b/crates/sqlbuild-python/src/bindings/_helpers/microbatches/projection.rs index 10c5c32f7e..f4f62ee23b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/microbatches/projection.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/microbatches/projection.rs @@ -12,10 +12,12 @@ use sqlbuild_state::microbatches::models::{ }; use crate::bindings::_helpers::boundary::keyword_request::Request; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::microbatches::conversion::{ - BoundParser, MODELS_MODULE, TYPES_MODULE, enum_text, optional_text, timestamp, + BoundParser, enum_text, optional_text, timestamp, }; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; const COMPLETION_RECORD_TYPES: [&str; 2] = ["partition_completion", "synthetic_completion"]; const ORDINARY_COMPLETION: &str = "partition_completion"; @@ -26,7 +28,7 @@ fn python_intervals<'py>( py: Python<'py>, intervals: &[TextInterval], ) -> PyResult> { - let class: Bound<'py, PyAny> = imported(py, MODELS_MODULE, "MicrobatchInterval")?; + let class: Bound<'py, PyAny> = host::entry(py, ExecutorHost::MicrobatchInterval)?; let built: Vec> = intervals .iter() .map(|interval| { @@ -48,7 +50,7 @@ fn python_coverage<'py>( events: &[Bound<'py, PyAny>], ) -> PyResult> { let projected_class: Bound<'py, PyAny> = - imported(py, MODELS_MODULE, "ProjectedMicrobatchInterval")?; + host::entry(py, ExecutorHost::ProjectedMicrobatchInterval)?; let mut projected: Vec> = Vec::with_capacity(projection.segments.len()); for segment in &projection.segments { let event: &Bound<'py, PyAny> = &events[segment.completion]; @@ -69,7 +71,7 @@ fn python_coverage<'py>( )?); } call_with( - &imported(py, MODELS_MODULE, "MicrobatchCoverageProjection")?, + &host::entry(py, ExecutorHost::MicrobatchCoverageProjection)?, &[ ("intervals", PyTuple::new(py, projected)?.into_any()), ( @@ -196,12 +198,12 @@ fn replay<'py>(request: Option<&Bound<'py, PyDict>>) -> PyResult) -> PyResult { - let constants: Bound<'_, PyModule> = py.import(CONSTANTS_MODULE)?; - let columns: Vec = constants.getattr("MICROBATCH_COLUMNS")?.extract()?; - let types: Bound<'_, PyAny> = constants.getattr("MICROBATCH_COLUMN_TYPES")?; + let columns: Vec = host::entry(py, ExecutorHost::MicrobatchColumns)?.extract()?; + let types: Bound<'_, PyAny> = host::entry(py, ExecutorHost::MicrobatchColumnTypes)?; let declared: Vec = columns .iter() .map(|column| types.get_item(column)?.str()?.extract()) @@ -56,8 +54,8 @@ impl Layout { /// One event's column literals in column order. fn literals(&self, event: &Bound<'_, PyAny>) -> PyResult> { let py: Python<'_> = event.py(); - let values: Bound<'_, PyAny> = - imported(py, CODEC_MODULE, "MicrobatchEventCodec")?.call_method1("values", (event,))?; + let values: Bound<'_, PyAny> = host::entry(py, ExecutorHost::MicrobatchEventCodec)? + .call_method1("values", (event,))?; values .try_iter()? .zip(&self.declared) @@ -76,7 +74,7 @@ fn qualified_table<'py>( (database, schema): (&Bound<'py, PyAny>, &Bound<'py, PyAny>), ) -> PyResult> { let py: Python<'py> = render_qualified_name.py(); - let table_name: Bound<'py, PyAny> = imported(py, CONSTANTS_MODULE, "MICROBATCH_TABLE_NAME")?; + let table_name: Bound<'py, PyAny> = host::entry(py, ExecutorHost::MicrobatchTableName)?; optional_text(&call_with( render_qualified_name, &[ @@ -161,7 +159,7 @@ fn read_scope( let table: String = scope_table(render_qualified_name, scope)?; let layout: Layout = Layout::new(py)?; let wildcard: String = - imported(py, CONSTANTS_MODULE, "MICROBATCH_GENERATION_WILDCARD")?.extract()?; + host::entry(py, ExecutorHost::MicrobatchGenerationWildcard)?.extract()?; let generation: Bound<'_, PyAny> = scope.getattr("physical_generation_id")?; let generation: Option = if generation.extract::()? == wildcard { None @@ -193,7 +191,7 @@ fn create_table(request: Option<&Bound<'_, PyDict>>) -> PyResult { )? .ok_or_else(|| state_error(py, MISSING_SCHEMA))?; let render_framework_type: Bound<'_, PyAny> = request.get("render_framework_type")?; - let framework: Bound<'_, PyAny> = imported(py, ADAPTER_TYPES, "FrameworkType")?; + let framework: Bound<'_, PyAny> = host::entry(py, AdapterHost::FrameworkType)?; let text_type: String = render_framework_type .call1((framework.getattr("STRING")?,))? .extract()?; @@ -230,16 +228,13 @@ fn create_indexes<'py>(request: Option<&Bound<'py, PyDict>>) -> PyResult = request.get("render_identifier")?; @@ -247,8 +242,7 @@ fn create_indexes<'py>(request: Option<&Bound<'py, PyDict>>) -> PyResult)> = Vec::new(); - let definitions: Bound<'_, PyAny> = - imported(py, CONSTANTS_MODULE, "MICROBATCH_DIRECT_INDEXES")?; + let definitions: Bound<'_, PyAny> = host::entry(py, ExecutorHost::MicrobatchDirectIndexes)?; for item in definitions.call_method0("items")?.try_iter()? { let (name, columns): (Bound<'_, PyAny>, Bound<'_, PyAny>) = item?.extract()?; let columns: Vec = columns diff --git a/crates/sqlbuild-python/src/bindings/_helpers/mod.rs b/crates/sqlbuild-python/src/bindings/_helpers/mod.rs index 9b7bb46cb6..1ad13fd9e6 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/mod.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/mod.rs @@ -56,6 +56,7 @@ pub(crate) mod macro_bridge; pub(crate) mod manifest; pub(crate) mod microbatches; pub(crate) mod model_config; +pub(crate) mod native_host; pub(crate) mod native_threads; pub(crate) mod output_capture; pub(crate) mod plan_command; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/model_config/config_errors.rs b/crates/sqlbuild-python/src/bindings/_helpers/model_config/config_errors.rs index 681cf918ef..8b0f142fd9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/model_config/config_errors.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/model_config/config_errors.rs @@ -5,13 +5,11 @@ use pyo3::types::{PyDict, PyDictMethods}; use pyo3::{Py, PyErr, pymethods}; use sqlbuild_model_config::errors::{ConfigError, ErrorClass}; -use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::model_config::config_values::config_value_type_error; use crate::bindings::_helpers::model_config::errors::NativeConfigError; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; -const DISCOVERY_ERRORS_MODULE: &str = "sqlbuild._native"; -const IDENTITY_ERRORS_MODULE: &str = "sqlbuild._native"; const EXPECTED_STRING: &str = "a string"; const COMPILE_INPUT_CLASS: &str = "compile_input"; @@ -82,15 +80,15 @@ impl NativeConfigError { } let keywords: Bound<'py, PyDict> = PyDict::new(py); keywords.set_item("help", &self.help)?; - let (module, class) = match self.class_name { - RESOURCE_IDENTITY_CLASS => (IDENTITY_ERRORS_MODULE, "ResourceIdentityError"), - DISCOVERY_CONFLICT_CLASS => (DISCOVERY_ERRORS_MODULE, "DiscoveryConflictError"), + let class: ErrorId = match self.class_name { + RESOURCE_IDENTITY_CLASS => ErrorId::ResourceIdentity, + DISCOVERY_CONFLICT_CLASS => ErrorId::DiscoveryConflict, _ => { keywords.set_item("code", self.code)?; - (COMPILE_ERRORS_MODULE, "CompileInputError") + ErrorId::CompileInput } }; - imported(py, module, class)?.call((&self.message,), Some(&keywords)) + registry::error(py, class)?.call((&self.message,), Some(&keywords)) } } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/model_config/config_templates.rs b/crates/sqlbuild-python/src/bindings/_helpers/model_config/config_templates.rs index e0375e37ef..4fba09abbf 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/model_config/config_templates.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/model_config/config_templates.rs @@ -21,13 +21,13 @@ use sqlbuild_model_config::templates::types::TemplateHost; use crate::bindings::_helpers::model_config::config_errors::native_config_error; use crate::bindings::_helpers::model_config::errors::NativeConfigError; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; const TEMPLATE_OPEN_TOKEN: &str = "${"; const ENVIRONMENT_READ: &str = "env"; const CONTEXT_READ: &str = "ctx"; const EFFECTIVE_VARS_LABEL: &str = "effective vars"; -const PROJECT_VAR_VALUES_MODULE: &str = "sqlbuild._native"; -const RENDER_PROJECT_VAR_TEXT: &str = "render_project_var_text"; const VALUE_PATH: &str = "its value"; const BUILTINS_MODULE: &str = "builtins"; const ASCII_FUNCTION: &str = "ascii"; @@ -241,11 +241,8 @@ impl<'py> TemplateHost for PythonHost<'py> { if value.is_exact_instance_of::() { return self.text_of(value); } - let rendered = self - .py - .import(PROJECT_VAR_VALUES_MODULE) - .and_then(|module| module.getattr(RENDER_PROJECT_VAR_TEXT)) - .and_then(|render| { + let rendered = + registry::native(self.py, NativeAttr::RenderProjectVarText).and_then(|render| { let arguments = PyDict::new(self.py); arguments.set_item("value", value)?; arguments.set_item("label", label)?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/model_config/config_values.rs b/crates/sqlbuild-python/src/bindings/_helpers/model_config/config_values.rs index f5ae424dd8..8b3caf15ae 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/model_config/config_values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/model_config/config_values.rs @@ -5,8 +5,9 @@ use pyo3::types::{PyBool, PyInt, PyList, PyString, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; -const EXCEPTIONS_MODULE: &str = "sqlbuild._native"; const DATETIME_MODULE: &str = "datetime"; /// `values.get(key)`, or `None` when the value is absent or `None`. @@ -32,7 +33,7 @@ pub(crate) fn config_value_type_error<'py>( let py = actual_type.py(); let type_name: String = actual_type.getattr("__name__")?.extract()?; call_with( - &imported(py, EXCEPTIONS_MODULE, "ConfigValueTypeError")?, + ®istry::error(py, ErrorId::ConfigValueType)?, &[ ( "message", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/model_config/contract_classes.rs b/crates/sqlbuild-python/src/bindings/_helpers/model_config/contract_classes.rs index 67869c37d5..a6dc231cfd 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/model_config/contract_classes.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/model_config/contract_classes.rs @@ -5,96 +5,55 @@ use pyo3::sync::PyOnceLock; use pyo3::types::{PyDict, PyDictMethods}; use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::third_party::ThirdPartyHost; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const AUDITING_MODELS_MODULE: &str = "sqlbuild._native"; const AUDITING_TYPES_MODULE: &str = "sqlbuild.compiler.auditing.types"; -const CONTRACT_MODELS_MODULE: &str = "sqlbuild._native"; -const CONTRACT_TYPES_MODULE: &str = "sqlbuild._native"; -const DLT_MODELS_MODULE: &str = "sqlbuild.integrations.dlt.models"; -const INGESTR_MODELS_MODULE: &str = "sqlbuild.integrations.ingestr.models"; -const CONTRACT_CLASSES: [(&str, &str, &str); 6] = [ - ("schema_column", CONTRACT_MODELS_MODULE, "SchemaColumn"), - ( - "schema_audit_instance", - CONTRACT_MODELS_MODULE, - "SchemaAuditInstance", - ), +const CONTRACT_CLASSES: [(&str, RecordId); 6] = [ + ("schema_column", RecordId::SchemaColumn), + ("schema_audit_instance", RecordId::SchemaAuditInstance), ( "schema_dynamic_column_family", - CONTRACT_MODELS_MODULE, - "SchemaDynamicColumnFamily", - ), - ("source_location", CONTRACT_MODELS_MODULE, "SourceLocation"), - ( - "measurement_thresholds", - AUDITING_MODELS_MODULE, - "MeasurementThresholds", + RecordId::SchemaDynamicColumnFamily, ), + ("source_location", RecordId::SourceLocation), + ("measurement_thresholds", RecordId::MeasurementThresholds), ( "measurement_threshold_bound", - AUDITING_MODELS_MODULE, - "MeasurementThresholdBound", + RecordId::MeasurementThresholdBound, ), ]; -const YAML_ENTRY_CLASSES: [(&str, &str, &str); 13] = [ - ( - "schema_seed_entry", - CONTRACT_MODELS_MODULE, - "SchemaSeedEntry", - ), - ( - "seed_csv_settings", - CONTRACT_MODELS_MODULE, - "SeedCsvSettings", - ), - ("source_entry", CONTRACT_MODELS_MODULE, "SourceEntry"), - ( - "source_column_entry", - CONTRACT_MODELS_MODULE, - "SourceColumnEntry", - ), - ( - "source_freshness_config", - CONTRACT_MODELS_MODULE, - "SourceFreshnessConfig", - ), +const YAML_ENTRY_CLASSES: [(&str, RecordId); 10] = [ + ("schema_seed_entry", RecordId::SchemaSeedEntry), + ("seed_csv_settings", RecordId::SeedCsvSettings), + ("source_entry", RecordId::SourceEntry), + ("source_column_entry", RecordId::SourceColumnEntry), + ("source_freshness_config", RecordId::SourceFreshnessConfig), ( "source_freshness_age_policy", - CONTRACT_MODELS_MODULE, - "SourceFreshnessAgePolicy", + RecordId::SourceFreshnessAgePolicy, ), ( "integration_loader_config", - CONTRACT_MODELS_MODULE, - "IntegrationLoaderConfig", - ), - ( - "ingestr_source_config", - INGESTR_MODELS_MODULE, - "IngestrSourceConfig", - ), - ("dlt_source_config", DLT_MODELS_MODULE, "DltSourceConfig"), - ( - "dlt_resource_config", - DLT_MODELS_MODULE, - "DltResourceConfig", - ), - ( - "source_write_strategy", - CONTRACT_TYPES_MODULE, - "SourceWriteStrategy", + RecordId::IntegrationLoaderConfig, ), + ("source_write_strategy", RecordId::SourceWriteStrategy), ( "source_freshness_strategy", - CONTRACT_TYPES_MODULE, - "SourceFreshnessStrategy", + RecordId::SourceFreshnessStrategy, ), ( "source_freshness_value_kind", - CONTRACT_TYPES_MODULE, - "SourceFreshnessValueKind", + RecordId::SourceFreshnessValueKind, ), ]; +const INTEGRATION_ENTRY_CLASSES: [(&str, ThirdPartyHost); 3] = [ + ("ingestr_source_config", ThirdPartyHost::IngestrSourceConfig), + ("dlt_source_config", ThirdPartyHost::DltSourceConfig), + ("dlt_resource_config", ThirdPartyHost::DltResourceConfig), +]; static CONTRACT_CLASS_TABLE: PyOnceLock> = PyOnceLock::new(); static YAML_ENTRY_CLASS_TABLE: PyOnceLock> = PyOnceLock::new(); @@ -115,8 +74,8 @@ pub(crate) fn yaml_entry_classes(py: Python<'_>) -> PyResult> fn built_contract_classes(py: Python<'_>) -> PyResult> { let classes: Bound<'_, PyDict> = PyDict::new(py); - for (key, module, name) in CONTRACT_CLASSES { - classes.set_item(key, imported(py, module, name)?)?; + for (key, class) in CONTRACT_CLASSES { + classes.set_item(key, registry::class(py, class)?)?; } classes.set_item( "severities", @@ -132,8 +91,11 @@ fn built_contract_classes(py: Python<'_>) -> PyResult> { fn built_yaml_entry_classes(py: Python<'_>) -> PyResult> { let classes: Bound<'_, PyDict> = PyDict::new(py); classes.set_item("contracts", contract_classes(py)?)?; - for (key, module, name) in YAML_ENTRY_CLASSES { - classes.set_item(key, imported(py, module, name)?)?; + for (key, class) in YAML_ENTRY_CLASSES { + classes.set_item(key, registry::class(py, class)?)?; + } + for (key, class) in INTEGRATION_ENTRY_CLASSES { + classes.set_item(key, host::entry(py, class)?)?; } Ok(classes) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/model_config/header_columns.rs b/crates/sqlbuild-python/src/bindings/_helpers/model_config/header_columns.rs index 574276f280..815c84dfbb 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/model_config/header_columns.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/model_config/header_columns.rs @@ -10,15 +10,13 @@ use sqlbuild_model_config::header_metadata::main::parse_schema_columns::parse_sc use sqlbuild_model_config::header_metadata::models::ColumnRequest; use crate::bindings::_helpers::boundary::panics::compiler_guard; -use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::model_config::authored_nodes::PyNode; use crate::bindings::_helpers::model_config::config_errors::config_exception; use crate::bindings::_helpers::model_config::config_errors::native_config_error; use crate::bindings::_helpers::model_config::contract_classes::contract_classes; use crate::bindings::_helpers::model_config::header_metadata::{ContractClasses, column_tuple}; - -const DISCOVERY_ERRORS_MODULE: &str = "sqlbuild._native"; -const DECLARATION_ERROR_CLASS: &str = "DeclarationParseError"; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; /// `(file_path, label)`: the path and label every message names. pub(crate) type ColumnSite<'a> = (&'a str, &'a str); @@ -62,8 +60,7 @@ fn parse_schema_columns<'py>( fn raised(py: Python<'_>, error: ConfigError, declaration_errors: bool) -> PyErr { let built = (|| { if declaration_errors && error.class == ErrorClass::CompileInput { - return imported(py, DISCOVERY_ERRORS_MODULE, DECLARATION_ERROR_CLASS)? - .call1((error.message,)); + return registry::error(py, ErrorId::DeclarationParse)?.call1((error.message,)); } let native = native_config_error(py, error)?.into_bound(py).into_any(); config_exception(&native, None) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/model_config/template_data.rs b/crates/sqlbuild-python/src/bindings/_helpers/model_config/template_data.rs index b78e53cfda..a3b1ae296a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/model_config/template_data.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/model_config/template_data.rs @@ -19,8 +19,9 @@ use crate::bindings::_helpers::model_config::config_templates::{ resolve_effective_vars, }; use crate::bindings::_helpers::model_config::unicode_environment::unicode_environment; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; const UUID_MODULE: &str = "uuid"; const ENVIRONMENT_READ: &str = "env"; const RUN_ID_KEY: &str = "run.id"; @@ -82,27 +83,26 @@ pub(crate) fn expanded_effective_vars<'py>( ) -> PyResult> { let py: Python<'py> = raw_values.py(); if let Some((name, path, byte_offset)) = lone_surrogate_site(raw_values)? { - let error: Bound<'py, PyAny> = imported(py, COMPILE_ERRORS_MODULE, "CompileInputError")? - .call( - (format!( - "Variable '{name}' holds a lone surrogate in {path} at UTF-8 byte \ + let error: Bound<'py, PyAny> = registry::error(py, ErrorId::CompileInput)?.call( + (format!( + "Variable '{name}' holds a lone surrogate in {path} at UTF-8 byte \ {byte_offset}, which is not valid Unicode text" - ),), - Some(&keywords( - py, - &[( - "help", - PyString::new( - py, - &format!( - "Set '{name}' to valid Unicode text in --vars or the [vars] \ + ),), + Some(&keywords( + py, + &[( + "help", + PyString::new( + py, + &format!( + "Set '{name}' to valid Unicode text in --vars or the [vars] \ table; the value is not shown because it may be a secret" - ), - ) - .into_any(), - )], - )?), - )?; + ), + ) + .into_any(), + )], + )?), + )?; return Err(PyErr::from_value(error)); } applied( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/model_config/unicode_environment.rs b/crates/sqlbuild-python/src/bindings/_helpers/model_config/unicode_environment.rs index 64069fdec1..2d3195aea4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/model_config/unicode_environment.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/model_config/unicode_environment.rs @@ -7,8 +7,8 @@ use pyo3::types::{PyBytes, PyBytesMethods, PyDict, PyDictMethods, PyString}; use pyo3::{pyclass, pymethods}; use crate::bindings::_helpers::compile_driver::python_calls::imported; - -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; /// `os.environ`, raising a clear error for a value holding undecodable bytes. #[pyclass(module = "sqlbuild._native", frozen, mapping)] @@ -46,7 +46,7 @@ impl UnicodeEnvironment { ), )?; Err(PyErr::from_value( - imported(py, COMPILE_ERRORS_MODULE, "CompileInputError")?.call( + registry::error(py, ErrorId::CompileInput)?.call( (format!( "Environment variable '{name}' is not valid UTF-8 text: byte {byte_offset} \ starts an invalid UTF-8 sequence" diff --git a/crates/sqlbuild-python/src/bindings/_helpers/native_host/adapter.rs b/crates/sqlbuild-python/src/bindings/_helpers/native_host/adapter.rs new file mode 100644 index 0000000000..bd79305828 --- /dev/null +++ b/crates/sqlbuild-python/src/bindings/_helpers/native_host/adapter.rs @@ -0,0 +1,262 @@ +//! Adapter runtime entry points: adapter discovery, relation inspection, type system and the shapes adapters consume. + +use crate::bindings::_helpers::native_host::models::{HostEdge, HostEntry, HostId, HostKind}; + +/// One adapter entry of `sqlbuild._native_host`, indexing `ENTRIES`. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum AdapterHost { + BaseAdapter, + AdapterUserError, + ColumnInfo, + ExpressionInferenceProfile, + RelationInfo, + RelationLookup, + RetentionRequest, + RowDiffSampling, + RowDiffTolerance, + RowDiffTolerances, + TableFreshnessMetadata, + TableFreshnessRequest, + BuiltinAdapter, + FrameworkType, + LifeCycleEventKind, + MigrationTransfer, + RetentionScope, + TableFreshnessStatus, + AdapterSupportsSessionWarehouse, + BuiltinAdapterClasses, + DiscoverProjectAdapters, + ResolveAdapter, + GetColumnsForInspection, + ListRelationsForInspection, + ActiveInspectionCatalog, + InspectionQuerySink, + OpenInspectionCatalog, + RecordInspectionQuery, + BuildRelationLookup, + ResolveRelationLocationQualifiedName, + RunBoundedInspections, + RunRecordedInspectionQuery, + InspectionQueryRecord, + StatementMetadataEffect, + ConditionalResultNullability, + FirstArgNullability, + NormalizeRelationType, + NormalizeType, + SafeCastNullability, +} + +/// Every adapter entry, in `AdapterHost` order. +pub(crate) const ENTRIES: &[HostEntry] = &[ + HostEntry { + name: "BaseAdapter", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "AdapterUserError", + kind: HostKind::Exception, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "ColumnInfo", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "ExpressionInferenceProfile", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "RelationInfo", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "RelationLookup", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "RetentionRequest", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "RowDiffSampling", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "RowDiffTolerance", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "RowDiffTolerances", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "TableFreshnessMetadata", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "TableFreshnessRequest", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "BuiltinAdapter", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "FrameworkType", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "LifeCycleEventKind", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "MigrationTransfer", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "RetentionScope", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "TableFreshnessStatus", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "adapter_supports_session_warehouse", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "builtin_adapter_classes", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "discover_project_adapters", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "resolve_adapter", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "get_columns_for_inspection", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "list_relations_for_inspection", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "active_inspection_catalog", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "inspection_query_sink", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "open_inspection_catalog", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "record_inspection_query", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "build_relation_lookup", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "resolve_relation_location_qualified_name", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "run_bounded_inspections", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "run_recorded_inspection_query", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "InspectionQueryRecord", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "StatementMetadataEffect", + kind: HostKind::Class, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "conditional_result_nullability", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "first_arg_nullability", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "normalize_relation_type", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "normalize_type", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, + HostEntry { + name: "safe_cast_nullability", + kind: HostKind::Function, + edge: HostEdge::Adapter, + }, +]; + +impl AdapterHost { + /// The entry's position in `HOST_ENTRIES`, usable in constant tables. + pub(crate) const fn id(self) -> HostId { + HostId { + edge: HostEdge::Adapter, + index: self as usize, + } + } +} + +impl From for HostId { + fn from(entry: AdapterHost) -> Self { + entry.id() + } +} diff --git a/crates/sqlbuild-python/src/bindings/_helpers/native_host/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/native_host/constants.rs new file mode 100644 index 0000000000..aa58559825 --- /dev/null +++ b/crates/sqlbuild-python/src/bindings/_helpers/native_host/constants.rs @@ -0,0 +1,20 @@ +//! The host module and the one table of every entry native code may reach through it. + +use crate::bindings::_helpers::native_host::models::HostEntry; +use crate::bindings::_helpers::native_host::{adapter, executor, facade, third_party, user_code}; + +/// The only `sqlbuild` Python module native code imports by path. +pub(crate) const HOST_MODULE: &str = "sqlbuild._native_host"; +/// The module a custom-rule host process runs with `python -m`, a user-code edge reached by path. +pub(crate) const CUSTOM_HOST_LAUNCH_MODULE: &str = + "sqlbuild.rule_engine._helpers.host.custom_host_launch"; +/// The host module function naming each entry's target module and attribute. +pub(crate) const HOST_TARGETS_FUNCTION: &str = "host_targets"; +/// Every host entry, one group per edge in `HostEdge` order. +pub(crate) const HOST_ENTRIES: [&[HostEntry]; 5] = [ + adapter::ENTRIES, + user_code::ENTRIES, + executor::ENTRIES, + third_party::ENTRIES, + facade::ENTRIES, +]; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/native_host/executor.rs b/crates/sqlbuild-python/src/bindings/_helpers/native_host/executor.rs new file mode 100644 index 0000000000..d9daaee8a8 --- /dev/null +++ b/crates/sqlbuild-python/src/bindings/_helpers/native_host/executor.rs @@ -0,0 +1,970 @@ +//! Executor hand-off and results, and the runtime services commands run inside. + +use crate::bindings::_helpers::native_host::models::{HostEdge, HostEntry, HostId, HostKind}; + +/// One executor entry of `sqlbuild._native_host`, indexing `ENTRIES`. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum ExecutorHost { + CostResourceContext, + RunCostSummary, + CostCapability, + CostStatus, + BoundSentinel, + ReadProcessResources, + SqlPrivateRecordMarker, + DiagnosticsContext, + LogDebugEvent, + LogSql, + ProcessResourceReporting, + ProcessResourceUsage, + ExecutorInputError, + CurrentAuditResultProjection, + AuditExecutionResult, + BuildCallbacks, + BuildCustomizations, + BuildExecutionResult, + BuildInitialState, + BuildRuntimeParams, + FunctionExecutionResult, + SeedExecutionResult, + BuildStatus, + BuildDestinationTransientModels, + BuildDestinationRetentionRequests, + CopyCloneFingerprints, + RunPrephaseCloneStream, + ExecuteClone, + CloneExecutionInput, + CloneItemResult, + CloneSourceEntries, + CloneStatus, + QueryDiffArtifactLifecycle, + FullDiffSizeGuardError, + ExecuteQueryDiff, + ExecuteDiff, + DiffExecutionOptions, + DiffExecutionResult, + FullDiffSizeLimits, + RowDiffSamplingOverride, + ExecuteJanitorPlan, + JanitorArchiveCandidate, + JanitorArchivedRelation, + JanitorBlockedSchema, + JanitorDeleteCandidate, + JanitorDirectStatePruneCandidate, + JanitorOldNameView, + JanitorOldNameViewPlanning, + JanitorPlan, + JanitorQueryDiffArtifactCandidate, + JanitorRelationKey, + JanitorSkippedRelation, + JanitorSkippedSchema, + RunLoadPipeline, + LoadCallbacks, + LoadExecutionResult, + LoadRuntimeParams, + MigrationRelationForLocation, + IsCompatibilityView, + AuditExecutionError, + AuditPipelineCallbacks, + RunAuditPipelineWithCallbacks, + RunBuildPipeline, + RunScenarioCapturePipeline, + RunScenarioLocalTestPipeline, + RunScenarioTestPipeline, + RunSeedPipeline, + RunTestPipeline, + SelectScenarioSnapshotCaptureCandidates, + TestPipelineCallbacks, + ExecutePythonChecks, + ExecuteIngressPythonLoaderNodes, + RunIngressPythonLoaderNodes, + CreateReadSidePythonExecutionTracker, + IngressCallbacks, + PythonCheckCallbacks, + PythonCheckExecutionResult, + PythonNodeExecutionResult, + PythonNodeRunState, + PythonNodeRuntime, + ModelExecutionResult, + ScenarioCaptureSettings, + ScenarioLocalReplaySource, + ScenarioSnapshotCaptureLimits, + ExecutionStatus, + SqlTestExecutionResult, + MicrobatchEventCodec, + MicrobatchColumns, + MicrobatchColumnTypes, + MicrobatchDirectIndexes, + MicrobatchGenerationWildcard, + MicrobatchTableName, + MicrobatchStateError, + MicrobatchCoverageProjection, + MicrobatchInterval, + ProjectedMicrobatchInterval, + ReplayRequirementProjection, + ReplayRequirementState, + TextComputeLogTee, + CaptureAlreadyExistsError, + CaptureNotFoundError, + CaptureStateError, + ComputeLogMetadataError, + ComputeLogPathError, + InvalidCaptureIdError, + InvalidComputeLogCursorError, + InvalidComputeLogLimitError, + CaptureByteCounts, + CaptureInventory, + CaptureInventoryItem, + CaptureMetadata, + ComputeLogReadChunk, + FinalCaptureMetadata, + PruneResult, + ComputeLogStream, + ConnectionHooks, + ExecutionResourceKind, + ExecutionHistoryStorageError, + IntegrityConflictError, + InvalidCursorError, + InvalidEventError, + InvalidFilterError, + InvalidLimitError, + ProjectionConsistencyError, + UnsupportedSchemaVersionError, + EventPage, + RunPage, + RunRecord, + StoredEvent, + RunStatus, + ExecutionDurationLimitError, + ExecutionDeadlineScope, + EventDispatcher, + OperationLifecycle, + ObservabilityValidationError, + CreateLifecycleEvent, + CurrentExecutionIdentity, + DiagnosticLogToJson, + DispatcherScope, + InvocationExternalContextScope, + InvocationScope, + LifecycleEventFromJson, + LifecycleEventToJson, + RunScope, + DiagnosticLog, + LifecycleEvent, + OperationAttributes, + CommandOutputValidationError, + OutputCaptureInputError, + ConfiguredOutputCaptureScope, + BoundCommandOutputSink, + CommandOutputCaptureSummary, + CommandOutputRecord, + OutputCaptureContext, + CommandOutputStream, + OutputRecordPriority, + SqlValueRenderingError, +} + +/// Every executor entry, in `ExecutorHost` order. +pub(crate) const ENTRIES: &[HostEntry] = &[ + HostEntry { + name: "CostResourceContext", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "RunCostSummary", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "CostCapability", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "CostStatus", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "BoundSentinel", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "read_process_resources", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "SQL_PRIVATE_RECORD_MARKER", + kind: HostKind::Value, + edge: HostEdge::Executor, + }, + HostEntry { + name: "diagnostics_context", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "log_debug_event", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "log_sql", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "process_resource_reporting", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ProcessResourceUsage", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ExecutorInputError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "current_audit_result_projection", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "AuditExecutionResult", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "BuildCallbacks", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "BuildCustomizations", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "BuildExecutionResult", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "BuildInitialState", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "BuildRuntimeParams", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "FunctionExecutionResult", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "SeedExecutionResult", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "BuildStatus", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "build_destination_transient_models", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "build_destination_retention_requests", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "copy_clone_fingerprints", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "run_prephase_clone_stream", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "execute_clone", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "CloneExecutionInput", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "CloneItemResult", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "CloneSourceEntries", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "CloneStatus", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "QueryDiffArtifactLifecycle", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "FullDiffSizeGuardError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "execute_query_diff", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "execute_diff", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "DiffExecutionOptions", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "DiffExecutionResult", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "FullDiffSizeLimits", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "RowDiffSamplingOverride", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "execute_janitor_plan", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "JanitorArchiveCandidate", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "JanitorArchivedRelation", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "JanitorBlockedSchema", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "JanitorDeleteCandidate", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "JanitorDirectStatePruneCandidate", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "JanitorOldNameView", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "JanitorOldNameViewPlanning", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "JanitorPlan", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "JanitorQueryDiffArtifactCandidate", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "JanitorRelationKey", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "JanitorSkippedRelation", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "JanitorSkippedSchema", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "run_load_pipeline", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "LoadCallbacks", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "LoadExecutionResult", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "LoadRuntimeParams", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "migration_relation_for_location", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "is_compatibility_view", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "AuditExecutionError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "AuditPipelineCallbacks", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "run_audit_pipeline_with_callbacks", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "run_build_pipeline", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "run_scenario_capture_pipeline", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "run_scenario_local_test_pipeline", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "run_scenario_test_pipeline", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "run_seed_pipeline", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "run_test_pipeline", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "select_scenario_snapshot_capture_candidates", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "TestPipelineCallbacks", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "execute_python_checks", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "execute_ingress_python_loader_nodes", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "run_ingress_python_loader_nodes", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "create_read_side_python_execution_tracker", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "IngressCallbacks", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "PythonCheckCallbacks", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "PythonCheckExecutionResult", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "PythonNodeExecutionResult", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "PythonNodeRunState", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "PythonNodeRuntime", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ModelExecutionResult", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ScenarioCaptureSettings", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ScenarioLocalReplaySource", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ScenarioSnapshotCaptureLimits", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ExecutionStatus", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "SqlTestExecutionResult", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "MicrobatchEventCodec", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "MICROBATCH_COLUMNS", + kind: HostKind::Value, + edge: HostEdge::Executor, + }, + HostEntry { + name: "MICROBATCH_COLUMN_TYPES", + kind: HostKind::Value, + edge: HostEdge::Executor, + }, + HostEntry { + name: "MICROBATCH_DIRECT_INDEXES", + kind: HostKind::Value, + edge: HostEdge::Executor, + }, + HostEntry { + name: "MICROBATCH_GENERATION_WILDCARD", + kind: HostKind::Value, + edge: HostEdge::Executor, + }, + HostEntry { + name: "MICROBATCH_TABLE_NAME", + kind: HostKind::Value, + edge: HostEdge::Executor, + }, + HostEntry { + name: "MicrobatchStateError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "MicrobatchCoverageProjection", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "MicrobatchInterval", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ProjectedMicrobatchInterval", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ReplayRequirementProjection", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ReplayRequirementState", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "TextComputeLogTee", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "CaptureAlreadyExistsError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "CaptureNotFoundError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "CaptureStateError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ComputeLogMetadataError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ComputeLogPathError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "InvalidCaptureIdError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "InvalidComputeLogCursorError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "InvalidComputeLogLimitError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "CaptureByteCounts", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "CaptureInventory", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "CaptureInventoryItem", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "CaptureMetadata", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ComputeLogReadChunk", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "FinalCaptureMetadata", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "PruneResult", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ComputeLogStream", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ConnectionHooks", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ExecutionResourceKind", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ExecutionHistoryStorageError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "IntegrityConflictError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "InvalidCursorError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "InvalidEventError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "InvalidFilterError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "InvalidLimitError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ProjectionConsistencyError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "UnsupportedSchemaVersionError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "EventPage", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "RunPage", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "RunRecord", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "StoredEvent", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "RunStatus", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ExecutionDurationLimitError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "execution_deadline_scope", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "EventDispatcher", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "OperationLifecycle", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "ObservabilityValidationError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "create_lifecycle_event", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "current_execution_identity", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "diagnostic_log_to_json", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "dispatcher_scope", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "invocation_external_context_scope", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "invocation_scope", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "lifecycle_event_from_json", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "lifecycle_event_to_json", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "run_scope", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "DiagnosticLog", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "LifecycleEvent", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "OperationAttributes", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "CommandOutputValidationError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "OutputCaptureInputError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, + HostEntry { + name: "configured_output_capture_scope", + kind: HostKind::Function, + edge: HostEdge::Executor, + }, + HostEntry { + name: "BoundCommandOutputSink", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "CommandOutputCaptureSummary", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "CommandOutputRecord", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "OutputCaptureContext", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "CommandOutputStream", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "OutputRecordPriority", + kind: HostKind::Class, + edge: HostEdge::Executor, + }, + HostEntry { + name: "SqlValueRenderingError", + kind: HostKind::Exception, + edge: HostEdge::Executor, + }, +]; + +impl ExecutorHost { + /// The entry's position in `HOST_ENTRIES`, usable in constant tables. + pub(crate) const fn id(self) -> HostId { + HostId { + edge: HostEdge::Executor, + index: self as usize, + } + } +} + +impl From for HostId { + fn from(entry: ExecutorHost) -> Self { + entry.id() + } +} diff --git a/crates/sqlbuild-python/src/bindings/_helpers/native_host/facade.rs b/crates/sqlbuild-python/src/bindings/_helpers/native_host/facade.rs new file mode 100644 index 0000000000..09f2323f5e --- /dev/null +++ b/crates/sqlbuild-python/src/bindings/_helpers/native_host/facade.rs @@ -0,0 +1,58 @@ +//! Public facade entry points: the CLI entry point, compile reuse and the package itself. + +use crate::bindings::_helpers::native_host::models::{HostEdge, HostEntry, HostId, HostKind}; + +/// One facade entry of `sqlbuild._native_host`, indexing `ENTRIES`. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum FacadeHost { + PackageFile, + DispatchWithComputeLogs, + ProviderSettingsInputs, + StartupInputs, + CompileReuseRequest, +} + +/// Every facade entry, in `FacadeHost` order. +pub(crate) const ENTRIES: &[HostEntry] = &[ + HostEntry { + name: "package_file", + kind: HostKind::Value, + edge: HostEdge::Facade, + }, + HostEntry { + name: "dispatch_with_compute_logs", + kind: HostKind::Function, + edge: HostEdge::Facade, + }, + HostEntry { + name: "provider_settings_inputs", + kind: HostKind::Function, + edge: HostEdge::Facade, + }, + HostEntry { + name: "startup_inputs", + kind: HostKind::Function, + edge: HostEdge::Facade, + }, + HostEntry { + name: "CompileReuseRequest", + kind: HostKind::Class, + edge: HostEdge::Facade, + }, +]; + +impl FacadeHost { + /// The entry's position in `HOST_ENTRIES`, usable in constant tables. + pub(crate) const fn id(self) -> HostId { + HostId { + edge: HostEdge::Facade, + index: self as usize, + } + } +} + +impl From for HostId { + fn from(entry: FacadeHost) -> Self { + entry.id() + } +} diff --git a/crates/sqlbuild-python/src/bindings/_helpers/native_host/host.rs b/crates/sqlbuild-python/src/bindings/_helpers/native_host/host.rs new file mode 100644 index 0000000000..29b9e8ec96 --- /dev/null +++ b/crates/sqlbuild-python/src/bindings/_helpers/native_host/host.rs @@ -0,0 +1,54 @@ +//! Resolve host entries: targets read once from `sqlbuild._native_host`, attributes read live. + +use pyo3::exceptions::PyImportError; +use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyResult, Python}; +use pyo3::sync::PyOnceLock; +use pyo3::types::{PyDict, PyDictMethods, PyString, PyTuple, PyTupleMethods}; + +use crate::bindings::_helpers::native_host::constants::{ + HOST_ENTRIES, HOST_MODULE, HOST_TARGETS_FUNCTION, +}; +use crate::bindings::_helpers::native_host::models::HostId; + +/// One entry's target module path and attribute name. +type Target = (Py, Py); + +/// Every entry's target, grouped and ordered as `HOST_ENTRIES`. +static TARGETS: PyOnceLock>> = PyOnceLock::new(); + +fn missing(name: &str) -> pyo3::PyErr { + PyImportError::new_err(format!("{HOST_MODULE} does not define host entry '{name}'")) +} + +/// Read every entry's target from the host module, failing on the first entry it lacks. +fn host_targets(py: Python<'_>) -> PyResult>> { + let declared: Bound<'_, PyDict> = py + .import(HOST_MODULE)? + .getattr(HOST_TARGETS_FUNCTION)? + .call0()? + .cast_into::()?; + let mut groups: Vec> = Vec::with_capacity(HOST_ENTRIES.len()); + for group in HOST_ENTRIES { + let mut targets: Vec = Vec::with_capacity(group.len()); + for entry in group { + let target: Bound<'_, PyTuple> = declared + .get_item(entry.name)? + .ok_or_else(|| missing(entry.name))? + .cast_into::()?; + targets.push(( + target.get_item(0)?.cast_into::()?.unbind(), + target.get_item(1)?.cast_into::()?.unbind(), + )); + } + groups.push(targets); + } + Ok(groups) +} + +/// The host entry `id`: its target module imported and the attribute read on every call. +pub(crate) fn entry<'py>(py: Python<'py>, id: impl Into) -> PyResult> { + let id: HostId = id.into(); + let targets: &Vec> = TARGETS.get_or_try_init(py, || host_targets(py))?; + let (module, attribute): &Target = &targets[id.edge as usize][id.index]; + py.import(module.bind(py))?.getattr(attribute.bind(py)) +} diff --git a/crates/sqlbuild-python/src/bindings/_helpers/native_host/mod.rs b/crates/sqlbuild-python/src/bindings/_helpers/native_host/mod.rs new file mode 100644 index 0000000000..c9c575a440 --- /dev/null +++ b/crates/sqlbuild-python/src/bindings/_helpers/native_host/mod.rs @@ -0,0 +1,11 @@ +//! The one Python module native code imports by path, and the staying-Python entries it names. + +pub(crate) mod adapter; +pub(crate) mod constants; +pub(crate) mod executor; +pub(crate) mod facade; +pub(crate) mod host; +pub(crate) mod models; +pub(crate) mod registration; +pub(crate) mod third_party; +pub(crate) mod user_code; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/native_host/models.rs b/crates/sqlbuild-python/src/bindings/_helpers/native_host/models.rs new file mode 100644 index 0000000000..a8bc406038 --- /dev/null +++ b/crates/sqlbuild-python/src/bindings/_helpers/native_host/models.rs @@ -0,0 +1,60 @@ +//! Host entries: the staying-Python names native code reaches through `sqlbuild._native_host`. + +/// What a host entry resolves to. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum HostKind { + Function, + Class, + Exception, + Value, +} + +impl HostKind { + /// The kind as the import test and `_native.HOST_ENTRIES` spell it. + pub(crate) const fn label(self) -> &'static str { + match self { + Self::Function => "function", + Self::Class => "class", + Self::Exception => "exception", + Self::Value => "value", + } + } +} + +/// Which boundary a host entry crosses; also its group in `HOST_ENTRIES`. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum HostEdge { + Adapter, + UserCode, + Executor, + ThirdParty, + Facade, +} + +impl HostEdge { + /// The edge as `_native.HOST_ENTRIES` spells it. + pub(crate) const fn label(self) -> &'static str { + match self { + Self::Adapter => "adapter", + Self::UserCode => "user_code", + Self::Executor => "executor", + Self::ThirdParty => "third_party", + Self::Facade => "facade", + } + } +} + +/// One host entry: its `sqlbuild._native_host` name, kind and edge. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) struct HostEntry { + pub(crate) name: &'static str, + pub(crate) kind: HostKind, + pub(crate) edge: HostEdge, +} + +/// A host entry by edge and position, from any edge's entry enum. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) struct HostId { + pub(crate) edge: HostEdge, + pub(crate) index: usize, +} diff --git a/crates/sqlbuild-python/src/bindings/_helpers/native_host/registration.rs b/crates/sqlbuild-python/src/bindings/_helpers/native_host/registration.rs new file mode 100644 index 0000000000..d6ae0baed6 --- /dev/null +++ b/crates/sqlbuild-python/src/bindings/_helpers/native_host/registration.rs @@ -0,0 +1,15 @@ +//! Register `_native.HOST_ENTRIES`, the host table the Python import test checks against. + +use pyo3::prelude::{Bound, PyModule, PyModuleMethods, PyResult}; +use pyo3::types::PyTuple; + +use crate::bindings::_helpers::native_host::constants::HOST_ENTRIES; + +pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { + let entries: Vec<(&str, &str, &str)> = HOST_ENTRIES + .iter() + .flat_map(|group| group.iter()) + .map(|entry| (entry.name, entry.kind.label(), entry.edge.label())) + .collect(); + module.add("HOST_ENTRIES", PyTuple::new(module.py(), entries)?) +} diff --git a/crates/sqlbuild-python/src/bindings/_helpers/native_host/third_party.rs b/crates/sqlbuild-python/src/bindings/_helpers/native_host/third_party.rs new file mode 100644 index 0000000000..8c65bee3db --- /dev/null +++ b/crates/sqlbuild-python/src/bindings/_helpers/native_host/third_party.rs @@ -0,0 +1,250 @@ +//! Glue around Python-only third-party integrations: dbt, dlt, ingestr, Dagster and rivers. + +use crate::bindings::_helpers::native_host::models::{HostEdge, HostEntry, HostId, HostKind}; + +/// One third-party entry of `sqlbuild._native_host`, indexing `ENTRIES`. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum ThirdPartyHost { + AssetSelectionCommands, + CheckMetadataExcludedKeys, + CloneCommand, + CompletedExecutionStatuses, + DagsterAssetEnabledField, + DagsterAssetNodeKinds, + DagsterCheckEnabledField, + DagsterCheckNameField, + DagsterDirectKindNodeKinds, + DefaultSelectableNodeKinds, + ExplicitSelectionFlags, + InsufficientCheckStatus, + LiveEventCommands, + LoaderNodeKind, + LoadSelectableNodeKinds, + MaterializableNodeKinds, + ModelNodeKind, + ScenarioCheckKind, + SourceNodeKind, + StandaloneCheckCommands, + SuccessExecutionStatus, + ViewMaterializationType, + WarningCheckSeverity, + DagsterDagInputError, + LoadDbtYaml, + RenderSelectedDbtProfileOutput, + DbtProfileError, + DltIntegrationKind, + DltIntegrationError, + DltLoaderFunction, + DltResourceConfig, + DltSourceConfig, + IngestrIntegrationKind, + IngestrIntegrationError, + IngestrLoaderFunction, + IngestrSourceConfig, + RiversAssetNodeKinds, +} + +/// Every third-party entry, in `ThirdPartyHost` order. +pub(crate) const ENTRIES: &[HostEntry] = &[ + HostEntry { + name: "ASSET_SELECTION_COMMANDS", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "CHECK_METADATA_EXCLUDED_KEYS", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "CLONE_COMMAND", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "COMPLETED_EXECUTION_STATUSES", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "DAGSTER_ASSET_ENABLED_FIELD", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "DAGSTER_ASSET_NODE_KINDS", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "DAGSTER_CHECK_ENABLED_FIELD", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "DAGSTER_CHECK_NAME_FIELD", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "DAGSTER_DIRECT_KIND_NODE_KINDS", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "DEFAULT_SELECTABLE_NODE_KINDS", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "EXPLICIT_SELECTION_FLAGS", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "INSUFFICIENT_CHECK_STATUS", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "LIVE_EVENT_COMMANDS", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "LOADER_NODE_KIND", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "LOAD_SELECTABLE_NODE_KINDS", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "MATERIALIZABLE_NODE_KINDS", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "MODEL_NODE_KIND", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "SCENARIO_CHECK_KIND", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "SOURCE_NODE_KIND", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "STANDALONE_CHECK_COMMANDS", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "SUCCESS_EXECUTION_STATUS", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "VIEW_MATERIALIZATION_TYPE", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "WARNING_CHECK_SEVERITY", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "DagsterDagInputError", + kind: HostKind::Exception, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "load_dbt_yaml", + kind: HostKind::Function, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "render_selected_dbt_profile_output", + kind: HostKind::Function, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "DbtProfileError", + kind: HostKind::Exception, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "DLT_INTEGRATION_KIND", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "DltIntegrationError", + kind: HostKind::Exception, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "dlt_loader_function", + kind: HostKind::Function, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "DltResourceConfig", + kind: HostKind::Class, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "DltSourceConfig", + kind: HostKind::Class, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "INGESTR_INTEGRATION_KIND", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "IngestrIntegrationError", + kind: HostKind::Exception, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "ingestr_loader_function", + kind: HostKind::Function, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "IngestrSourceConfig", + kind: HostKind::Class, + edge: HostEdge::ThirdParty, + }, + HostEntry { + name: "RIVERS_ASSET_NODE_KINDS", + kind: HostKind::Value, + edge: HostEdge::ThirdParty, + }, +]; + +impl ThirdPartyHost { + /// The entry's position in `HOST_ENTRIES`, usable in constant tables. + pub(crate) const fn id(self) -> HostId { + HostId { + edge: HostEdge::ThirdParty, + index: self as usize, + } + } +} + +impl From for HostId { + fn from(entry: ThirdPartyHost) -> Self { + entry.id() + } +} diff --git a/crates/sqlbuild-python/src/bindings/_helpers/native_host/user_code.rs b/crates/sqlbuild-python/src/bindings/_helpers/native_host/user_code.rs new file mode 100644 index 0000000000..12156b2506 --- /dev/null +++ b/crates/sqlbuild-python/src/bindings/_helpers/native_host/user_code.rs @@ -0,0 +1,370 @@ +//! Entry points that run user Python: macros, Python nodes, providers, custom rules and sinks. + +use crate::bindings::_helpers::native_host::models::{HostEdge, HostEntry, HostId, HostKind}; + +/// One user-code entry of `sqlbuild._native_host`, indexing `ENTRIES`. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum UserCodeHost { + PythonResourceRows, + TargetContextWithMacroCallStore, + InventoryProjectMacros, + LoadProjectMacros, + MacroInvocationContext, + DiscoverCommandOutputSinkFunctions, + ImportPythonRootModule, + InstallPythonRootPackage, + LoadMaterializeFunction, + LoadProviderModule, + LoadPythonNodeModule, + LoadSinkModule, + ModuleFunctions, + ModuleProviderClasses, + ParameterAnnotations, + ProviderUsages, + RequiresArguments, + RestorePythonRootModules, + SourceLine, + ValidateEventExporterSignature, + ValidateDiscoveredInputs, + DiscoverRuntimeExtensions, + RunWithMacroBridge, + MacroStoreModulePaths, + ActiveMacroBridge, + LoadCustomMaterializations, + PythonNodeDependencyKey, + PythonNodeIdentity, + BuildHookIdentities, + PythonNodeKind, + PythonNodeStatus, + ProviderInputError, + BuildProviderSession, + ReadAssetDefinition, + ReadAuditFactoryDefinition, + ReadCheckDefinition, + ReadFactoryDefinition, + ReadHookDefinition, + ReadLoaderDefinition, + ReadTaskDefinition, + AuditCase, + RelationRenderer, + SqlResourceRef, + PythonCheckSeverity, + SqlResourceRefKind, + LoadCustomRules, + CustomRulePayloads, + FactOutcomeDigest, + FullFactKeys, + IsTreeFact, + SharedFactEncoder, + BuildRuleFactViews, + EventExporterInputError, + EventExporterStateError, + InvokeLifecycleEventSink, + RunCommandOutputSinks, + GetLifecycleEventSinkDefinition, +} + +/// Every user-code entry, in `UserCodeHost` order. +pub(crate) const ENTRIES: &[HostEntry] = &[ + HostEntry { + name: "python_resource_rows", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "target_context_with_macro_call_store", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "inventory_project_macros", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "load_project_macros", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "macro_invocation_context", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "discover_command_output_sink_functions", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "import_python_root_module", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "install_python_root_package", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "load_materialize_function", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "load_provider_module", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "load_python_node_module", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "load_sink_module", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "module_functions", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "module_provider_classes", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "parameter_annotations", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "provider_usages", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "requires_arguments", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "restore_python_root_modules", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "source_line", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "validate_event_exporter_signature", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "validate_discovered_inputs", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "discover_runtime_extensions", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "run_with_macro_bridge", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "macro_store_module_paths", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "active_macro_bridge", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "load_custom_materializations", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "python_node_dependency_key", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "python_node_identity", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "build_hook_identities", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "PythonNodeKind", + kind: HostKind::Class, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "PythonNodeStatus", + kind: HostKind::Class, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "ProviderInputError", + kind: HostKind::Exception, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "build_provider_session", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "read_asset_definition", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "read_audit_factory_definition", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "read_check_definition", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "read_factory_definition", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "read_hook_definition", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "read_loader_definition", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "read_task_definition", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "AuditCase", + kind: HostKind::Class, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "RELATION_RENDERER", + kind: HostKind::Value, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "SqlResourceRef", + kind: HostKind::Class, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "PythonCheckSeverity", + kind: HostKind::Class, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "SqlResourceRefKind", + kind: HostKind::Class, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "load_custom_rules", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "custom_rule_payloads", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "fact_outcome_digest", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "full_fact_keys", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "is_tree_fact", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "shared_fact_encoder", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "build_rule_fact_views", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "EventExporterInputError", + kind: HostKind::Exception, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "EventExporterStateError", + kind: HostKind::Exception, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "invoke_lifecycle_event_sink", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "run_command_output_sinks", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, + HostEntry { + name: "get_lifecycle_event_sink_definition", + kind: HostKind::Function, + edge: HostEdge::UserCode, + }, +]; + +impl UserCodeHost { + /// The entry's position in `HOST_ENTRIES`, usable in constant tables. + pub(crate) const fn id(self) -> HostId { + HostId { + edge: HostEdge::UserCode, + index: self as usize, + } + } +} + +impl From for HostId { + fn from(entry: UserCodeHost) -> Self { + entry.id() + } +} diff --git a/crates/sqlbuild-python/src/bindings/_helpers/output_capture/context.rs b/crates/sqlbuild-python/src/bindings/_helpers/output_capture/context.rs index de6696dc5b..2334c9d65f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/output_capture/context.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/output_capture/context.rs @@ -13,7 +13,8 @@ use pyo3::types::PyTuple; use crate::bindings::_helpers::compile_driver::python_calls::{ call_with, context_variable, imported, }; -use crate::bindings::_helpers::output_capture::records::MODELS_MODULE; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; const CONTEXT_VARIABLE: &str = "sqlbuild_output_capture_context"; static CURRENT_CONTEXT: PyOnceLock> = PyOnceLock::new(); @@ -29,7 +30,7 @@ pub(crate) struct CommandOutputContext { impl CommandOutputContext { fn __enter__(&self, py: Python<'_>) -> PyResult<()> { let context = call_with( - &imported(py, MODELS_MODULE, "OutputCaptureContext")?, + &host::entry(py, ExecutorHost::OutputCaptureContext)?, &[( "external_context", imported(py, "builtins", "dict")?.call1((self.external_context.bind(py),))?, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/output_capture/dispatcher.rs b/crates/sqlbuild-python/src/bindings/_helpers/output_capture/dispatcher.rs index 37ae8d21a6..9d0ce25de1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/output_capture/dispatcher.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/output_capture/dispatcher.rs @@ -23,9 +23,10 @@ use sqlbuild_core::command_output::models::{ }; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::native_threads::constants::OUTPUT_SUPPRESSED_ATTRIBUTE; use crate::bindings::_helpers::native_threads::exit_signal::signal_interpreter_exit; -use crate::bindings::_helpers::output_capture::records::{MODELS_MODULE, TYPES_MODULE}; use crate::bindings::_helpers::output_capture::values::{ freeze, object, output_error, pass_text, python_text, unencodable, }; @@ -188,7 +189,7 @@ impl Shared { ("occurred_at", self.occurred_at(py)?), ( "stream", - imported(py, TYPES_MODULE, "CommandOutputStream")? + host::entry(py, ExecutorHost::CommandOutputStream)? .call1((stream_value(draft.stream),))?, ), ("message", python_text(py, &draft.message)?), @@ -197,7 +198,7 @@ impl Shared { ("chunk_count", object(py, draft.chunk_count)?), ( "priority", - imported(py, TYPES_MODULE, "OutputRecordPriority")? + host::entry(py, ExecutorHost::OutputRecordPriority)? .call1((priority_value(draft.priority),))?, ), ]; @@ -206,7 +207,7 @@ impl Shared { fields.push(("dropped_records", object(py, draft.dropped_records)?)); } call_with( - &imported(py, MODELS_MODULE, "CommandOutputRecord")?, + &host::entry(py, ExecutorHost::CommandOutputRecord)?, &fields, ) } @@ -337,7 +338,7 @@ impl Shared { } fn input_error(py: Python<'_>, message: &str) -> PyErr { - output_error(py, "OutputCaptureInputError", message) + output_error(py, ExecutorHost::OutputCaptureInputError, message) } #[pymethods] @@ -547,7 +548,7 @@ impl OutputCaptureDispatcher { } let (accepted, delivered, failed, dropped, depth, capacity, flush_complete) = counts; call_with( - &imported(py, MODELS_MODULE, "CommandOutputCaptureSummary")?, + &host::entry(py, ExecutorHost::CommandOutputCaptureSummary)?, &[ ("accepted", object(py, accepted)?), ("delivered", object(py, delivered)?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/output_capture/records.rs b/crates/sqlbuild-python/src/bindings/_helpers/output_capture/records.rs index 33943740ea..bb6c15c7bb 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/output_capture/records.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/output_capture/records.rs @@ -16,12 +16,12 @@ use sqlbuild_core::json::main::dumps::dumps; use sqlbuild_core::json::models::{JsonDialect, JsonValue, StdlibJsonOptions}; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::output_capture::values::{ freeze, json_value, object, output_error, plain_int, validation_error, }; -pub(crate) const MODELS_MODULE: &str = "sqlbuild.runtime.output_capture.models"; -pub(crate) const TYPES_MODULE: &str = "sqlbuild.runtime.output_capture.types"; const ENVELOPE_FIELDS: [&str; 16] = [ "record_id", "record_type", @@ -96,20 +96,14 @@ fn record_shape(record: &Bound<'_, PyAny>) -> PyResult { }, sequence: plain_int(&field("sequence")?), occurred_at_is_utc: utc_aware(&field("occurred_at")?)?, - stream_is_known: field("stream")?.is_instance(&imported( - py, - TYPES_MODULE, - "CommandOutputStream", - )?)?, + stream_is_known: field("stream")? + .is_instance(&host::entry(py, ExecutorHost::CommandOutputStream)?)?, message_is_text: field("message")?.is_instance_of::(), chunk_index: plain_int(&field("chunk_index")?), chunk_count: plain_int(&field("chunk_count")?), dropped_records: plain_int(&field("dropped_records")?), - priority_is_known: field("priority")?.is_instance(&imported( - py, - TYPES_MODULE, - "OutputRecordPriority", - )?)?, + priority_is_known: field("priority")? + .is_instance(&host::entry(py, ExecutorHost::OutputRecordPriority)?)?, record_type: optional_text(&field("record_type")?)?, schema_version: plain_int(&field("schema_version")?), }) @@ -186,17 +180,17 @@ fn record_from_fields<'py>(data: &Bound<'py, PyDict>) -> PyResult occurred_at(&value(name)?)?, "stream" => { - imported(py, TYPES_MODULE, "CommandOutputStream")?.call1((value(name)?,))? + host::entry(py, ExecutorHost::CommandOutputStream)?.call1((value(name)?,))? } "priority" => { - imported(py, TYPES_MODULE, "OutputRecordPriority")?.call1((value(name)?,))? + host::entry(py, ExecutorHost::OutputRecordPriority)?.call1((value(name)?,))? } _ => value(name)?, }; fields.push((name, converted)); } call_with( - &imported(py, MODELS_MODULE, "CommandOutputRecord")?, + &host::entry(py, ExecutorHost::CommandOutputRecord)?, &fields, ) } @@ -256,11 +250,7 @@ pub(crate) fn command_output_from_json<'py>( "command output external_context must be an object", )); } - let validation_class = imported( - py, - crate::bindings::_helpers::output_capture::values::EXCEPTIONS_MODULE, - "CommandOutputValidationError", - )?; + let validation_class = host::entry(py, ExecutorHost::CommandOutputValidationError)?; let record = record_from_fields(data).map_err(|error| { let typed = error.is_instance_of::(py) || error.is_instance_of::(py); @@ -283,7 +273,7 @@ fn validate_depth(value: &Bound<'_, PyAny>, depth: usize) -> Result<(), PyErr> { if depth > MAX_INVOCATION_CONTEXT_DEPTH { return Err(output_error( value.py(), - "OutputCaptureInputError", + ExecutorHost::OutputCaptureInputError, &format!( "{INVOCATION_CONTEXT_ENV} must not exceed {MAX_INVOCATION_CONTEXT_DEPTH} levels" ), @@ -302,7 +292,7 @@ fn validate_depth(value: &Bound<'_, PyAny>, depth: usize) -> Result<(), PyErr> { } fn input_error(py: Python<'_>, message: &str) -> PyErr { - output_error(py, "OutputCaptureInputError", message) + output_error(py, ExecutorHost::OutputCaptureInputError, message) } /// Validated opaque invocation metadata supplied by an integration through the environment. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/output_capture/values.rs b/crates/sqlbuild-python/src/bindings/_helpers/output_capture/values.rs index d78dee5f8e..3060ce4e39 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/output_capture/values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/output_capture/values.rs @@ -13,14 +13,15 @@ use sqlbuild_core::json::models::{JsonInteger, JsonValue}; use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::macro_bridge::store_keys::text; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; -pub(crate) const EXCEPTIONS_MODULE: &str = "sqlbuild.runtime.output_capture.exceptions"; const SURROGATE_PASS: &str = "surrogatepass"; const UTF8: &str = "utf-8"; /// The exception class `name` of the output capture exceptions module, raised with `message`. -pub(crate) fn output_error(py: Python<'_>, name: &str, message: &str) -> PyErr { - match imported(py, EXCEPTIONS_MODULE, name).and_then(|class| class.call1((message,))) { +pub(crate) fn output_error(py: Python<'_>, name: ExecutorHost, message: &str) -> PyErr { + match host::entry(py, name).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } @@ -28,7 +29,7 @@ pub(crate) fn output_error(py: Python<'_>, name: &str, message: &str) -> PyErr { /// `CommandOutputValidationError(message)`. pub(crate) fn validation_error(py: Python<'_>, message: &str) -> PyErr { - output_error(py, "CommandOutputValidationError", message) + output_error(py, ExecutorHost::CommandOutputValidationError, message) } /// A Python `str` as `surrogatepass` UTF-8 bytes. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/plan_command/plan.rs b/crates/sqlbuild-python/src/bindings/_helpers/plan_command/plan.rs index bd714e7a32..2db6d6e932 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/plan_command/plan.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/plan_command/plan.rs @@ -10,14 +10,12 @@ use crate::bindings::_helpers::build_command::support::keyword_relay; use crate::bindings::_helpers::cli::progress_text::with_verbose_inspection; use crate::bindings::_helpers::command_runtime::invocation::resolve_adapter_connection_context; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const EXTERNAL_REFS_MODULE: &str = "sqlbuild._native"; -const CONTRACT_MODELS: &str = "sqlbuild.runtime.contracts.models"; -const PRESENTATION_MODELS: &str = "sqlbuild._native"; const COMMAND_NAME: &str = "plan"; const DEFAULT_SECTION_ENTRIES: i64 = 50; const CONNECTION_TIMING: [&str; 2] = ["connection_count", "elapsed_seconds"]; @@ -66,7 +64,7 @@ fn resolve_invocation<'py>(request: &Bound<'py, PyAny>) -> PyResult = call_with( - &imported(py, NATIVE_MODULE, "discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[ ("project_dir", project_dir.clone()), ("extract_output_column_locations", flag(py, false)), @@ -76,7 +74,7 @@ fn resolve_invocation<'py>(request: &Bound<'py, PyAny>) -> PyResult = resolve_adapter_connection_context( &discovered, &project_dir, @@ -93,7 +91,7 @@ fn resolve_invocation<'py>(request: &Bound<'py, PyAny>) -> PyResult = call_with( - &imported(py, NATIVE_MODULE, "ConnectionProgressReporter")?, + ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, &[ ("adapter_name", context.getattr("adapter_name")?), ("stream", stream.clone()), @@ -101,7 +99,7 @@ fn resolve_invocation<'py>(request: &Bound<'py, PyAny>) -> PyResult = call_with( - &imported(py, NATIVE_MODULE, "PlanningProgressReporter")?, + ®istry::native(py, NativeAttr::PlanningProgressReporter)?, &[ ("stream", stream.clone()), ("use_color", flag(py, use_color)), @@ -110,7 +108,7 @@ fn resolve_invocation<'py>(request: &Bound<'py, PyAny>) -> PyResult = request.getattr("load_sources")?; let load_sources: Bound<'py, PyAny> = if requested_load.is_none() { call_with( - &imported(py, NATIVE_MODULE, "build_effective_settings_config")?, + ®istry::native(py, NativeAttr::BuildEffectiveSettingsConfig)?, &[("discovered_inputs", discovered.clone())], )? .getattr("auto_load_sources")? @@ -134,7 +132,7 @@ fn connection_hooks<'py>(invocation: &PlanInvocation<'py>) -> PyResult = invocation.stream.py(); let connection: &Bound<'py, PyAny> = &invocation.connection_progress; call_with( - &imported(py, CONTRACT_MODELS, "ConnectionHooks")?, + &host::entry(py, ExecutorHost::ConnectionHooks)?, &[ ( "on_progress", @@ -168,11 +166,7 @@ fn compile_pipeline<'py>( ) -> PyResult> { let py: Python<'py> = request.py(); let resolver: Bound<'py, PyAny> = call_with( - &imported( - py, - EXTERNAL_REFS_MODULE, - "resolve_external_sql_reference_resolver", - )?, + ®istry::native(py, NativeAttr::ResolveExternalSqlReferenceResolver)?, &[ ("project_dir", invocation.project_dir.clone()), ("discovered_inputs", invocation.discovered.clone()), @@ -213,9 +207,9 @@ fn compile_pipeline<'py>( )?; options.set_item("record_migration_fingerprints", false)?; let pipeline_options: Bound<'py, PyAny> = - imported(py, NATIVE_RECORDS_MODULE, "CompilePipelineOptions")?.call((), Some(&options))?; + registry::class(py, RecordId::CompilePipelineOptions)?.call((), Some(&options))?; call_with( - &imported(py, NATIVE_MODULE, "run_compile_pipeline")?, + ®istry::native(py, NativeAttr::RunCompilePipeline)?, &[ ("discovered_inputs", invocation.discovered.clone()), ("adapter", invocation.context.getattr("adapter")?), @@ -235,7 +229,7 @@ fn write_output( let python_entries: Bound<'_, PyAny> = pipeline.getattr("python_plan_entries")?; if request.getattr("json_output")?.is_truthy()? { let text: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "format_plan_json")?, + ®istry::native(py, NativeAttr::FormatPlanJson)?, &[("plan", plan), ("python_plan_entries", python_entries)], )?; imported(py, "builtins", "print")?.call1((text,))?; @@ -251,14 +245,14 @@ fn write_output( }, )?; let text: String = call_with( - &imported(py, NATIVE_MODULE, "format_plan")?, + ®istry::native(py, NativeAttr::FormatPlan)?, &[ ("plan", plan), ("full_refresh", request.getattr("full_refresh")?), ("use_color", flag(py, invocation.use_color)), ( "display_options", - imported(py, PRESENTATION_MODELS, "DisplayOptions")?.call((), Some(&display))?, + registry::class(py, RecordId::DisplayOptions)?.call((), Some(&display))?, ), ("python_plan_entries", python_entries), ("include_direct_freshness_diagnostics", flag(py, false)), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/plan_command/rules.rs b/crates/sqlbuild-python/src/bindings/_helpers/plan_command/rules.rs index d26831217b..3057151148 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/plan_command/rules.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/plan_command/rules.rs @@ -8,11 +8,9 @@ use pyo3::wrap_pyfunction; use crate::bindings::_helpers::command_runtime::adapters::resolve_cli_adapter; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const RULE_MODELS: &str = "sqlbuild._native"; -const REJECT_UNIT_TESTS_MODULE: &str = "sqlbuild._native"; -const GRAPH_SELECTION_MODULE: &str = "sqlbuild._native"; const LIST_ACTION: &str = "list"; const SHOW_ACTION: &str = "show"; const SKILLS_ACTION: &str = "skills"; @@ -52,7 +50,7 @@ fn run_skills( let py: Python<'_> = request.py(); let check: bool = request.getattr("skills_check")?.is_truthy()?; let fresh: bool = call_with( - &imported(py, NATIVE_MODULE, "install_rules_skills")?, + ®istry::native(py, NativeAttr::InstallRulesSkills)?, &[ ("config", config.clone()), ("project_dir", project_dir.clone()), @@ -130,7 +128,7 @@ fn show_rule( let rule: Bound<'_, PyAny> = rule?; if rule.getattr("code")?.extract::()? == selector { let text: String = call_with( - &imported(py, NATIVE_MODULE, "format_rule")?, + ®istry::native(py, NativeAttr::FormatRule)?, &[("rule", rule), ("config", config.clone())], )? .extract()?; @@ -156,13 +154,13 @@ fn rule_inputs<'py>( ) -> PyResult> { let py: Python<'py> = request.py(); let discovered: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[("project_dir", project_dir.clone())], )?; let select: Bound<'py, PyAny> = request.getattr("select")?; let exclude: Bound<'py, PyAny> = request.getattr("exclude")?; call_with( - &imported(py, REJECT_UNIT_TESTS_MODULE, "reject_unit_test_selectors")?, + ®istry::native(py, NativeAttr::RejectUnitTestSelectors)?, &[ ("select", select.clone()), ("exclude", exclude.clone()), @@ -170,7 +168,7 @@ fn rule_inputs<'py>( ], )?; let adapter_name: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), @@ -178,14 +176,14 @@ fn rule_inputs<'py>( )?; let adapter: Bound<'py, PyAny> = resolve_cli_adapter(&adapter_name, project_dir)?; let graph: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "build_project_graph")?, + ®istry::native(py, NativeAttr::BuildProjectGraph)?, &[ ("discovered_inputs", discovered.clone()), ("adapter", adapter.clone()), ], )?; let selected: Bound<'py, PyAny> = call_with( - &imported(py, GRAPH_SELECTION_MODULE, "select_project_graph")?, + ®istry::native(py, NativeAttr::SelectProjectGraph)?, &[ ("graph", graph.clone()), ("select", select), @@ -232,7 +230,7 @@ fn run_rule_selection( replaced.set_item("ignore", PyTuple::empty(py))?; let dialect: Bound<'_, PyAny> = adapter.call_method0("sql_analysis_dialect")?; let result: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "run_rules")?, + ®istry::native(py, NativeAttr::RunRules)?, &[ ("graph", graph), ("discovered_inputs", discovered), @@ -260,7 +258,7 @@ fn run_rule_selection( ], )?; let note: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "format_skipped_type_proof_rules")?, + ®istry::native(py, NativeAttr::FormatSkippedTypeProofRules)?, &[("codes", result.getattr("skipped_type_proof_rules")?)], )?; if !note.is_none() { @@ -271,11 +269,11 @@ fn run_rule_selection( fields.set_item(field, result.getattr(field)?)?; } let text: String = call_with( - &imported(py, NATIVE_MODULE, "format_rules_result")?, + ®istry::native(py, NativeAttr::FormatRulesResult)?, &[ ( "result", - imported(py, RULE_MODELS, "RulesResult")?.call((), Some(&fields))?, + registry::class(py, RecordId::RulesResult)?.call((), Some(&fields))?, ), ("json_output", request.getattr("json_output")?), ], @@ -308,7 +306,7 @@ fn run_rules_command(request: &Bound<'_, PyAny>) -> PyResult { } .call_method0("resolve")?; let config: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "load_rules_config")?, + ®istry::native(py, NativeAttr::LoadRulesConfig)?, &[("project_dir", project_dir.clone())], )?; let action: String = request.getattr("action")?.extract()?; @@ -316,7 +314,7 @@ fn run_rules_command(request: &Bound<'_, PyAny>) -> PyResult { return run_skills(request, &config, &project_dir); } let catalogue: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "build_rule_catalogue")?, + ®istry::native(py, NativeAttr::BuildRuleCatalogue)?, &[ ("config", config.clone()), ("project_dir", project_dir.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/plan_output/cursor_details.rs b/crates/sqlbuild-python/src/bindings/_helpers/plan_output/cursor_details.rs index fbf071b610..f3d929fdb1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/plan_output/cursor_details.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/plan_output/cursor_details.rs @@ -2,15 +2,16 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; -use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::cursor::conversion::GRAIN_NAMES; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::plan_output::display::{ aligned_timestamp_bounds, resolve_effective_microbatch_batch_size, }; use crate::bindings::_helpers::plan_output::values::{optional_text, text}; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const BOUND_TYPES_MODULE: &str = "sqlbuild.cursor_algebra.types"; -const CURSOR_MODELS_MODULE: &str = "sqlbuild._native"; const MICROBATCH_MODE: &str = "microbatch"; const TIMESTAMP_CURSOR: &str = "timestamp"; const INTEGER_CURSOR: &str = "integer"; @@ -84,14 +85,14 @@ fn count_batches( let (start, end) = (bounds.getattr("start")?, bounds.getattr("end")?); if cursor_type == TIMESTAMP_CURSOR { let duration: Bound<'_, PyAny> = - imported(py, CURSOR_MODELS_MODULE, "Duration")?.call_method1("parse", (batch_size,))?; + registry::class(py, RecordId::Duration)?.call_method1("parse", (batch_size,))?; if duration.is_none() { return Ok(None); } let temporal = |value: &Bound<'_, PyAny>| -> PyResult { Ok( - value.is_instance(&imported(py, CURSOR_MODELS_MODULE, "DateValue")?)? - || value.is_instance(&imported(py, CURSOR_MODELS_MODULE, "TimestampValue")?)?, + value.is_instance(®istry::class(py, RecordId::DateValue)?)? + || value.is_instance(®istry::class(py, RecordId::TimestampValue)?)?, ) }; if !temporal(&start)? || !temporal(&end)? { @@ -110,7 +111,7 @@ fn count_batches( if cursor_type != INTEGER_CURSOR { return Ok(None); } - let integer: Bound<'_, PyAny> = imported(py, CURSOR_MODELS_MODULE, "IntegerValue")?; + let integer: Bound<'_, PyAny> = registry::class(py, RecordId::IntegerValue)?; if !start.is_instance(&integer)? || !end.is_instance(&integer)? { return Ok(None); } @@ -152,7 +153,7 @@ pub(crate) fn cursor_details<'py>( let bounds: Bound<'py, PyAny> = entry.getattr("cursor_bounds")?; (!bounds.is_none()).then_some(bounds) }; - let sentinel: Bound<'py, PyAny> = imported(py, BOUND_TYPES_MODULE, "BoundSentinel")?; + let sentinel: Bound<'py, PyAny> = host::entry(py, ExecutorHost::BoundSentinel)?; if let Some(bounds) = &resolved_bounds && (bounds.getattr("start")?.is_instance(&sentinel)? || bounds.getattr("end")?.is_instance(&sentinel)?) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/plan_output/display.rs b/crates/sqlbuild-python/src/bindings/_helpers/plan_output/display.rs index f7eb47e474..0d964e8bab 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/plan_output/display.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/plan_output/display.rs @@ -23,10 +23,9 @@ use crate::bindings::_helpers::cursor::scalars::{ render_value, to_exclusive, to_inclusive, try_parse, }; use crate::bindings::_helpers::plan_output::values::{optional_text, text}; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const PLANNER_TYPES: &str = "sqlbuild._native"; -const PLANNER_MODELS: &str = "sqlbuild._native"; -const CURSOR_MODELS: &str = "sqlbuild._native"; const DATETIME_MODULE: &str = "datetime"; fn label_facts(entry: &Bound<'_, PyAny>) -> PyResult { @@ -70,7 +69,7 @@ fn cursor_typing<'py>( } else { PyString::new(py, DEFAULT_CURSOR_GRAIN).into_any() }; - let grain: Bound<'py, PyAny> = imported(py, PLANNER_TYPES, "CursorGrain")?.call1((grain,))?; + let grain: Bound<'py, PyAny> = registry::class(py, RecordId::CursorGrain)?.call1((grain,))?; Ok((effective, grain)) } @@ -110,7 +109,7 @@ fn resolve_bounded_cursor_override<'py>( return Ok(None); }; call_with( - &imported(py, PLANNER_MODELS, "CursorBounds")?, + ®istry::class(py, RecordId::CursorBounds)?, &[("start", start), ("end", to_exclusive(&end, &grain)?)], ) .map(Some) @@ -216,7 +215,7 @@ pub(crate) fn resolve_effective_microbatch_batch_size( effective_grain: &str, ) -> Result { effective_microbatch_batch_size(batch_size, effective_grain).map_err(|error| { - imported(py, PLANNER_TYPES, "CursorGrain") + registry::class(py, RecordId::CursorGrain) .and_then(|grain| grain.call1((error.0,))) .err() .unwrap_or_else(|| PyValueError::new_err("unknown cursor grain")) @@ -226,7 +225,7 @@ pub(crate) fn resolve_effective_microbatch_batch_size( fn as_datetime<'py>(value: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = value.py(); let inner: Bound<'py, PyAny> = value.getattr("value")?; - if value.is_instance(&imported(py, CURSOR_MODELS, "TimestampValue")?)? { + if value.is_instance(®istry::class(py, RecordId::TimestampValue)?)? { return Ok(inner); } let datetime: Bound<'py, PyAny> = imported(py, DATETIME_MODULE, "datetime")?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/plan_output/plan_text.rs b/crates/sqlbuild-python/src/bindings/_helpers/plan_output/plan_text.rs index 30084fa8d7..930041c4ef 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/plan_output/plan_text.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/plan_output/plan_text.rs @@ -12,7 +12,7 @@ use sqlbuild_core::cli_text::models::{ }; use sqlbuild_core::plan_display::main::format_query_diff::format_query_diff; -use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; use crate::bindings::_helpers::cursor::scalars::sentinel_to_token; use crate::bindings::_helpers::plan_output::cursor_details::{ CursorDetails, DEFERRED, RESOLVED, cursor_details, runtime_inputs_clause, @@ -27,8 +27,9 @@ use crate::bindings::_helpers::plan_output::python_identity::{ definition_source_text, dependency_source_text, }; use crate::bindings::_helpers::plan_output::values::{items, optional_text, text, texts}; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const DISPLAY_MODELS_MODULE: &str = "sqlbuild._native"; const SKIP_ACTION: &str = "skip"; const QUERY_CHANGED: &str = "query_changed"; const CONFIG_CHANGED: &str = "config_changed"; @@ -404,7 +405,7 @@ fn report(arguments: &KeywordArguments<'_>) -> PyResult { let display_options: Bound<'_, PyAny> = { let given: Bound<'_, PyAny> = arguments.value("display_options")?; if given.is_none() { - imported(py, DISPLAY_MODELS_MODULE, "DisplayOptions")?.call0()? + registry::class(py, RecordId::DisplayOptions)?.call0()? } else { given } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/actions.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/actions.rs index baad948897..052731e1ce 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/actions.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/actions.rs @@ -5,6 +5,8 @@ use pyo3::types::{PyDict, PyDictMethods}; use pyo3::{pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; const INCREMENTAL_MATERIALIZATION: &str = "incremental"; @@ -20,7 +22,7 @@ fn resolve_model_actions<'py>( let py: Python<'py> = scope.py(); let model_kind: Bound<'py, PyAny> = imported(py, COMPILE_TYPES_MODULE, "CompiledResourceType")?.getattr("MODEL")?; - let action_class: Bound<'py, PyAny> = imported(py, "sqlbuild._native", "ResolvedModelAction")?; + let action_class: Bound<'py, PyAny> = registry::class(py, RecordId::ResolvedModelAction)?; let selected: Bound<'py, PyAny> = scope.getattr("selected_keys")?; let models_by_name: Bound<'py, PyAny> = scope.getattr("models_by_name")?; let model_changes: Bound<'py, PyAny> = changes.getattr("models")?; @@ -42,7 +44,7 @@ fn resolve_model_actions<'py>( )?; } call_with( - &imported(py, "sqlbuild._native", "PlannerResolvedActions")?, + ®istry::class(py, RecordId::PlannerResolvedActions)?, &[("models", resolved.into_any())], ) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/detect.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/detect.rs index 7fd07624a1..e7b772f5f6 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/detect.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/detect.rs @@ -24,6 +24,9 @@ use sqlbuild_analysis::planner_changes::models::{ use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, }; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; use crate::bindings::_helpers::planner_changes::renames::origin_names; use crate::bindings::_helpers::planner_changes::results::{ backfill_result, change_kind, planner_enum, replay_policy_error, schema_findings, @@ -35,13 +38,10 @@ use crate::bindings::_helpers::planner_identity::metadata::{ compiled_function_fingerprint_sql, function_local_hashes, model_declared_columns_hash, model_version_identity_metadata_json, }; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::type_system::equality::types_equal as native_types_equal; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const HOOK_IDENTITIES_MODULE: &str = "sqlbuild.compiler.python_nodes.main._hook_identities"; -const DYNAMIC_FAMILIES_MODULE: &str = "sqlbuild._native"; -const DEBUG_EVENT_MODULE: &str = "sqlbuild.diagnostics.main.log_debug_event"; -const LOG_SQL_MODULE: &str = "sqlbuild.diagnostics.main.log_sql"; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; const CHANGES_LOGGER: &str = "sqlbuild.planner.changes"; const REPLAY_CONFIG_KEY: &str = "replay_on_change"; @@ -87,7 +87,7 @@ fn detect_changes<'py>( ), )?; let functions: Bound<'py, PyDict> = PyDict::new(py); - let result_class: Bound<'py, PyAny> = imported(py, "sqlbuild._native", "FunctionChangeResult")?; + let result_class: Bound<'py, PyAny> = registry::class(py, RecordId::FunctionChangeResult)?; let selected: Bound<'py, PyAny> = scope.getattr("selected_keys")?; let recorded: Bound<'py, PyAny> = snapshot.getattr("fingerprints")?.getattr("functions")?; let query_change_tracking: bool = project @@ -128,12 +128,12 @@ fn detect_changes<'py>( "no_change" } }; - arguments.push(("reason", planner_enum(py, "PlanReason", reason)?)); + arguments.push(("reason", planner_enum(py, RecordId::PlanReason, reason)?)); } functions.set_item(name, call_with(&result_class, &arguments)?)?; } call_with( - &imported(py, "sqlbuild._native", "PlannerChangeResults")?, + ®istry::class(py, RecordId::PlannerChangeResults)?, &[ ("models", models.into_any()), ("functions", functions.into_any()), @@ -241,9 +241,8 @@ fn scope_model_changes<'py>( )? .into_any(); let hook_version_hashes: Bound<'py, PyDict> = PyDict::new(py); - let identities: Bound<'py, PyAny> = - imported(py, HOOK_IDENTITIES_MODULE, "build_hook_identities")? - .call1((project.getattr("hook_functions")?,))?; + let identities: Bound<'py, PyAny> = host::entry(py, UserCodeHost::BuildHookIdentities)? + .call1((project.getattr("hook_functions")?,))?; for item in identities.call_method0("items")?.try_iter()? { let item: Bound<'py, PyAny> = item?; hook_version_hashes.set_item( @@ -429,7 +428,7 @@ pub(crate) fn model_change<'py>( fn replay_policy(model: &Bound<'_, PyAny>) -> PyResult { let py: Python<'_> = model.py(); let raw: Option = call_with( - &imported(py, NATIVE_MODULE, "get_config_str")?, + ®istry::native(py, NativeAttr::GetConfigStr)?, &[ ("values", model.getattr("config")?.getattr("values")?), ("key", PyString::new(py, REPLAY_CONFIG_KEY).into_any()), @@ -467,7 +466,7 @@ fn log_query_check( imported(py, "logging", "getLogger")?.call1((CHANGES_LOGGER,))?; let text = |value: &str| PyString::new(py, value).into_any(); call_with( - &imported(py, DEBUG_EVENT_MODULE, "log_debug_event")?, + &host::entry(py, ExecutorHost::LogDebugEvent)?, &[ ("logger", logger.clone()), ( @@ -492,7 +491,7 @@ fn log_query_check( ), ], )?; - let log_sql: Bound<'_, PyAny> = imported(py, LOG_SQL_MODULE, "log_sql")?; + let log_sql: Bound<'_, PyAny> = host::entry(py, ExecutorHost::LogSql)?; call_with( &log_sql, &[ @@ -597,7 +596,7 @@ fn schema_findings_of<'py>( let mut types_equal = |left: &str, right: &str| -> PyResult { native_types_equal(dialect, left, right) }; let matching_function: Bound<'py, PyAny> = - imported(py, DYNAMIC_FAMILIES_MODULE, "matching_dynamic_families")?; + registry::native(py, NativeAttr::MatchingDynamicFamilies)?; let mut single_family_type = |name: &str| -> PyResult> { let Some(families) = &dynamic else { return Ok(None); @@ -733,7 +732,7 @@ impl<'py> ChangeResult<'py> { } fn finish(self, backfill: Backfill, kind: &str) -> PyResult> { - let kind: Bound<'py, PyAny> = native_enum(self.py, "ChangeKind", kind)?; + let kind: Bound<'py, PyAny> = native_enum(self.py, RecordId::ChangeKind, kind)?; self.finish_kind(backfill, kind) } @@ -749,7 +748,7 @@ impl<'py> ChangeResult<'py> { .map(|flag| flag.to_owned().into_any()) }; call_with( - &imported(py, "sqlbuild._native", "ChangeDetectionResult")?, + ®istry::class(py, RecordId::ChangeDetectionResult)?, &[ ("model_name", self.model_name), ("change_kind", kind), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/renames.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/renames.rs index b6ec6ecca0..dc1bbb01b7 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/renames.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/renames.rs @@ -15,9 +15,9 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, }; use crate::bindings::_helpers::planner_identity::hashing::model_query_hash; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const REFERENCE_TYPES_MODULE: &str = "sqlbuild._native"; const GENERIC_DIALECT: &str = "generic"; const FINAL_NEWLINE: &str = "\n"; @@ -46,7 +46,7 @@ pub(crate) fn origin_names( .filter(|name| !name.is_empty()) .unwrap_or_else(|| GENERIC_DIALECT.to_owned()); let mut spans = |names: &BTreeSet| ref_name_spans(&query_sql, &dialect_name, names); - let compute_hash: Bound<'_, PyAny> = imported(py, NATIVE_MODULE, "compute_query_hash")?; + let compute_hash: Bound<'_, PyAny> = registry::native(py, NativeAttr::ComputeQueryHash)?; let mut query_hash = |text: &str| -> PyResult { call_with( &compute_hash, @@ -186,7 +186,7 @@ fn plan_reference_renames<'py>( let target_name: Bound<'py, PyAny> = runtime .getattr("project")? .getattr("effective_target_name")?; - let rename_class: Bound<'py, PyAny> = imported(py, "sqlbuild._native", "ReferenceRename")?; + let rename_class: Bound<'py, PyAny> = registry::class(py, RecordId::ReferenceRename)?; let mut renames: Vec> = Vec::new(); for item in planned.call_method0("items")?.try_iter()? { let item: Bound<'py, PyAny> = item?; @@ -236,7 +236,7 @@ fn changed_referencing_query( } let recorded_models: Bound<'_, PyAny> = snapshot.getattr("fingerprints")?.getattr("models")?; let ref_kind: Bound<'_, PyAny> = - imported(py, REFERENCE_TYPES_MODULE, "SqlReferenceKind")?.getattr("REF")?; + registry::class(py, RecordId::SqlReferenceKind)?.getattr("REF")?; let dialect: Option = snapshot.getattr("column_dialect")?.extract()?; for model in selected { let recorded: Bound<'_, PyAny> = diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/results.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/results.rs index 9151fe572d..bd2d0137ba 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/results.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/results.rs @@ -9,15 +9,13 @@ use sqlbuild_analysis::planner_changes::models::{ Backfill, BackfillAction, ChangeKind, SchemaChangeKind, SchemaColumnSource, SchemaFindingRow, }; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; - -pub(crate) const MODELS_MODULE: &str = "sqlbuild._native"; -pub(crate) const TYPES_MODULE: &str = "sqlbuild._native"; -const EXCEPTIONS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; /// `PlannerInputError(message)` for an unknown replay policy. pub(crate) fn replay_policy_error(py: Python<'_>, error: UnknownReplayPolicy) -> PyErr { - match imported(py, EXCEPTIONS_MODULE, "PlannerInputError") + match registry::error(py, ErrorId::PlannerInput) .and_then(|class| class.call1((error.message(),))) { Ok(value) => PyErr::from_value(value), @@ -28,19 +26,19 @@ pub(crate) fn replay_policy_error(py: Python<'_>, error: UnknownReplayPolicy) -> /// `TYPES_MODULE.(value)`. pub(crate) fn planner_enum<'py>( py: Python<'py>, - name: &str, + name: RecordId, value: &str, ) -> PyResult> { - imported(py, TYPES_MODULE, name)?.call1((value,)) + registry::class(py, name)?.call1((value,)) } /// A member of a natively declared planner enumeration, from its value. pub(crate) fn native_enum<'py>( py: Python<'py>, - name: &str, + name: RecordId, value: &str, ) -> PyResult> { - imported(py, "sqlbuild._native", name)?.call1((value,)) + registry::class(py, name)?.call1((value,)) } /// `BackfillResult(action=..., duration=...)`. @@ -54,9 +52,12 @@ pub(crate) fn backfill_result<'py>( BackfillAction::ForwardOnly => "forward", }; call_with( - &imported(py, "sqlbuild._native", "BackfillResult")?, + ®istry::class(py, RecordId::BackfillResult)?, &[ - ("action", planner_enum(py, "BackfillAction", action)?), + ( + "action", + planner_enum(py, RecordId::BackfillAction, action)?, + ), ( "duration", backfill.duration.clone().into_pyobject(py)?.into_any(), @@ -74,7 +75,7 @@ pub(crate) fn change_kind<'py>(py: Python<'py>, kind: ChangeKind) -> PyResult "config_changed", ChangeKind::NoChange => "no_change", }; - native_enum(py, "ChangeKind", value) + native_enum(py, RecordId::ChangeKind, value) } /// A tuple of `SchemaFinding` values. @@ -82,7 +83,7 @@ pub(crate) fn schema_findings<'py>( py: Python<'py>, findings: &[SchemaFindingRow], ) -> PyResult> { - let class: Bound<'py, PyAny> = imported(py, MODELS_MODULE, "SchemaFinding")?; + let class: Bound<'py, PyAny> = registry::class(py, RecordId::SchemaFinding)?; let mut items: Vec> = Vec::with_capacity(findings.len()); for finding in findings { let kind: &str = match finding.kind { @@ -97,12 +98,15 @@ pub(crate) fn schema_findings<'py>( items.push(call_with( &class, &[ - ("kind", native_enum(py, "SchemaChangeKind", kind)?), + ("kind", native_enum(py, RecordId::SchemaChangeKind, kind)?), ( "column_name", finding.column_name.clone().into_pyobject(py)?.into_any(), ), - ("source", native_enum(py, "SchemaColumnSource", source)?), + ( + "source", + native_enum(py, RecordId::SchemaColumnSource, source)?, + ), ( "expected_type", finding.expected_type.clone().into_pyobject(py)?.into_any(), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/clone_plans.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/clone_plans.rs index c7327b2447..d8f8bdae34 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/clone_plans.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/clone_plans.rs @@ -1,9 +1,11 @@ //! Clone planning: destination and origin entries, and deferred-clone graph traversal. -use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; use crate::bindings::_helpers::planner_entries::values::{ - COMPILE_MODELS, native, planner_model, planner_type, string, + native, planner_model, planner_type, string, }; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyBool, PyDict, PyDictMethods, PyFrozenSet, PySet, PySetMethods, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; @@ -45,12 +47,11 @@ fn clone_plan_output<'py>( ) -> PyResult> { let py = project.py(); let upstream = - imported(py, "sqlbuild._native", "build_execution_upstream_deps")?.call1((project,))?; - let downstream = - imported(py, "sqlbuild._native", "build_downstream_deps")?.call1((&upstream,))?; + registry::native(py, NativeAttr::BuildExecutionUpstreamDeps)?.call1((project,))?; + let downstream = registry::native(py, NativeAttr::BuildDownstreamDeps)?.call1((&upstream,))?; let selected = native( py, - "resolve_graph_selection", + NativeAttr::ResolveGraphSelection, &[ ("graph", project.getattr("lineage_graph")?), ("select", select.clone()), @@ -59,13 +60,13 @@ fn clone_plan_output<'py>( )?; planner_model( py, - "PlanOutput", + RecordId::PlanOutput, &[ ( "execution_order", native( py, - "topologically_order_keys", + NativeAttr::TopologicallyOrderKeys, &[("upstream", upstream.clone())], )?, ), @@ -93,17 +94,17 @@ fn clone_resolution<'py>( let py = project.py(); let model_locations = native( py, - "build_model_locations", + NativeAttr::BuildModelLocations, &[("models", project.getattr("models")?)], )?; let seed_locations = native( py, - "build_seed_locations", + NativeAttr::BuildSeedLocations, &[("seeds", project.getattr("seeds")?)], )?; let function_locations = native( py, - "build_function_locations", + NativeAttr::BuildFunctionLocations, &[("functions", project.getattr("functions")?)], )?; let source_map = PyDict::new(py); @@ -113,7 +114,7 @@ fn clone_resolution<'py>( } let source_warehouse_columns = native( py, - "gather_source_columns", + NativeAttr::GatherSourceColumns, &[ ("project", source_project.clone()), ("adapter", adapter.clone()), @@ -142,14 +143,17 @@ fn model_entry<'py>( }; planner_model( py, - "ModelPlanEntry", + RecordId::ModelPlanEntry, &[ ("key", model.getattr("key")?), ("name", model.getattr("name")?), ("relative_path", model.getattr("relative_path")?), ("materialization_type", materialization_type), - ("action", planner_type(py, "PlanAction", action)?), - ("reason", planner_type(py, "PlanReason", "NO_CHANGE")?), + ("action", planner_type(py, RecordId::PlanAction, action)?), + ( + "reason", + planner_type(py, RecordId::PlanReason, "NO_CHANGE")?, + ), ("destination", model.getattr("destination")?), ("fingerprint_query_sql", string(py, "")), ("resolved_sql", resolved_sql), @@ -172,8 +176,11 @@ fn clone_model_entries<'py>( if !selected.contains(model.getattr("key")?)? || disabled_model(&model)? { continue; } - let materialization_type = - native(py, "get_materialization_type", &[("model", model.clone())])?; + let materialization_type = native( + py, + NativeAttr::GetMaterializationType, + &[("model", model.clone())], + )?; let resolved_sql = if materialization_type.eq(VIEW_MATERIALIZATION)? { let functions = PyDict::new(py); for function in project.getattr("functions")?.try_iter()? { @@ -182,7 +189,7 @@ fn clone_model_entries<'py>( } let context = native( py, - "ModelPlanContext", + RecordId::ModelPlanContext, &[ ("model_locations", resolution.model_locations.clone()), ("models_by_name", PyDict::new(py).into_any()), @@ -202,25 +209,28 @@ fn clone_model_entries<'py>( )?; native( py, - "resolve_model_sql", + NativeAttr::ResolveModelSql, &[ ("adapter", adapter.clone()), ("model", model.clone()), - ("snapshot", native(py, "WarehouseSnapshot", &[])?), + ("snapshot", native(py, RecordId::WarehouseSnapshot, &[])?), ("context", context), ( "backfill", native( py, - "BackfillResult", + RecordId::BackfillResult, &[( "action", - planner_type(py, "BackfillAction", "FORWARD_ONLY")?, + planner_type(py, RecordId::BackfillAction, "FORWARD_ONLY")?, )], )?, ), ("full_refresh", PyBool::new(py, false).to_owned().into_any()), - ("cursor_overrides", native(py, "CursorOverridePair", &[])?), + ( + "cursor_overrides", + native(py, RecordId::CursorOverridePair, &[])?, + ), ], )? } else { @@ -264,7 +274,7 @@ fn clone_source_entries<'py>( let qualified = adapter .getattr("render_qualified_name")? .call((), Some(&kwargs))?; - let destination = imported(py, COMPILE_MODELS, "CompiledRelationLocation")?.call( + let destination = registry::class(py, RecordId::CompiledRelationLocation)?.call( (), Some(&{ let location = PyDict::new(py); @@ -279,7 +289,7 @@ fn clone_source_entries<'py>( source.getattr("key")?, planner_model( py, - "CloneSourcePlanEntry", + RecordId::CloneSourcePlanEntry, &[ ("key", source.getattr("key")?), ("name", source.getattr("name")?), @@ -307,7 +317,7 @@ fn clone_function_entries<'py>( } let body_sql = native( py, - "resolve_function_sql", + NativeAttr::ResolveFunctionSql, &[ ("adapter", adapter.clone()), ("function", function.clone()), @@ -328,7 +338,7 @@ fn clone_function_entries<'py>( let field = |name: &str| function.getattr(name); let entry = planner_model( py, - "FunctionPlanEntry", + RecordId::FunctionPlanEntry, &[ ("key", field("key")?), ("name", field("name")?), @@ -341,7 +351,7 @@ fn clone_function_entries<'py>( "fingerprint_query_sql", native( py, - "build_compiled_function_fingerprint_sql", + NativeAttr::BuildCompiledFunctionFingerprintSql, &[("function", function.clone())], )?, ), @@ -363,7 +373,7 @@ fn seed_entry<'py>(seed: &Bound<'py, PyAny>) -> PyResult> { let py = seed.py(); planner_model( py, - "SeedPlanEntry", + RecordId::SeedPlanEntry, &[ ("key", seed.getattr("key")?), ("name", seed.getattr("name")?), @@ -408,8 +418,11 @@ fn origin_model_entries<'py>( if !selected_names.contains(model.getattr("name")?)? || disabled_model(&model)? { continue; } - let materialization_type = - native(py, "get_materialization_type", &[("model", model.clone())])?; + let materialization_type = native( + py, + NativeAttr::GetMaterializationType, + &[("model", model.clone())], + )?; entries.push(model_entry(&model, materialization_type, string(py, ""))?); } tuple_of(py, entries) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/command_plans.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/command_plans.rs index c3402cd2e6..d8d83ae37b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/command_plans.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/command_plans.rs @@ -8,6 +8,7 @@ use crate::bindings::_helpers::planner_entries::relations::planner_relations_con use crate::bindings::_helpers::planner_entries::values::{ boolean, native, none, planner_model, planner_type, }; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyFrozenSet, PyList, PyListMethods, PyString, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; @@ -67,7 +68,7 @@ fn base_plan_output<'py>( project.getattr("enforce_explicit_references")?, ), ]); - planner_model(py, "PlanOutput", &fields) + planner_model(py, RecordId::PlanOutput, &fields) } /// Resolve command selection through the canonical build selector phase. @@ -81,7 +82,7 @@ fn resolve_static_command_scope<'py>( let py = project.py(); native( py, - "resolve_planner_scopes", + NativeAttr::ResolvePlannerScopes, &[ ("project", project.clone()), ("selection", selection.clone()), @@ -89,7 +90,7 @@ fn resolve_static_command_scope<'py>( "policies", planner_model( py, - "PlannerPolicies", + RecordId::PlannerPolicies, &[("auto_load_sources", boolean(py, auto_load_sources)?)], )?, ), @@ -155,13 +156,17 @@ fn build_audit_command_plan<'py>( let model = model?; materializations.set_item( model.getattr("name")?, - native(py, "get_materialization_type", &[("model", model.clone())])? - .getattr("value")?, + native( + py, + NativeAttr::GetMaterializationType, + &[("model", model.clone())], + )? + .getattr("value")?, )?; } let audits = native( py, - "build_selected_audit_entries", + NativeAttr::BuildSelectedAuditEntries, &[ ("project", project.clone()), ("adapter", adapter.clone()), @@ -238,7 +243,7 @@ fn build_test_command_plan<'py>( } let fingerprint_sql = native( py, - "build_compiled_function_fingerprint_sql", + NativeAttr::BuildCompiledFunctionFingerprintSql, &[("function", function.clone())], )?; functions.push(function_entry( @@ -248,7 +253,7 @@ fn build_test_command_plan<'py>( ( fingerprint_sql, none(py), - planner_type(py, "PlanReason", "NO_CHANGE")?, + planner_type(py, RecordId::PlanReason, "NO_CHANGE")?, ), )?); } @@ -300,7 +305,7 @@ fn build_seed_command_plan<'py>( if !selected_keys.contains(seed.getattr("key")?)? { continue; } - let identity = native(py, "build_seed_identity", &[("seed", seed.clone())])?; + let identity = native(py, NativeAttr::BuildSeedIdentity, &[("seed", seed.clone())])?; let (version_hash, metadata_json) = (identity.get_item(0)?, identity.get_item(1)?); let previous = existing.call_method1("get", (seed.getattr("name")?,))?; let reason = if previous.is_none() { @@ -312,7 +317,7 @@ fn build_seed_command_plan<'py>( }; entries.push(planner_model( py, - "SeedPlanEntry", + RecordId::SeedPlanEntry, &[ ("key", seed.getattr("key")?), ("name", seed.getattr("name")?), @@ -323,7 +328,11 @@ fn build_seed_command_plan<'py>( ), ( "columns", - native(py, "extract_seed_columns", &[("seed", seed.clone())])?, + native( + py, + NativeAttr::ExtractSeedColumns, + &[("seed", seed.clone())], + )?, ), ( "csv_settings", @@ -332,7 +341,7 @@ fn build_seed_command_plan<'py>( ("fingerprint_definition", metadata_json.clone()), ("fingerprint_version_hash", version_hash), ("fingerprint_metadata_json", metadata_json), - ("reason", planner_type(py, "PlanReason", reason)?), + ("reason", planner_type(py, RecordId::PlanReason, reason)?), ], )?); } @@ -414,7 +423,7 @@ fn read_selected_seed_fingerprints<'py>( let node_names = PyTuple::new(py, names.iter())?; let latest = native( py, - "read_latest_fingerprints", + NativeAttr::ReadLatestFingerprints, &[ ("connection", connection.clone()), ("execute", adapter.getattr("execute")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/display_plan.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/display_plan.rs index c938ed6c98..889c8ea83b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/display_plan.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/display_plan.rs @@ -1,9 +1,10 @@ //! Display-only plans for external plan surfaces when warehouse planning cannot resolve. -use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::planner_entries::values::{ - NATIVE_MODULE, boolean, native, none, planner_model, planner_type, string, + boolean, native, none, planner_model, planner_type, string, }; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyFrozenSet, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; @@ -34,7 +35,7 @@ fn display_action( fn config<'py>(model: &Bound<'py, PyAny>, key: &str) -> PyResult> { native( model.py(), - "get_config_str", + NativeAttr::GetConfigStr, &[ ("values", model.getattr("config")?.getattr("values")?), ("key", string(model.py(), key)), @@ -47,10 +48,14 @@ fn display_entry<'py>( full_refresh: bool, ) -> PyResult> { let py = model.py(); - let materialization_type = native(py, "get_materialization_type", &[("model", model.clone())])?; + let materialization_type = native( + py, + NativeAttr::GetMaterializationType, + &[("model", model.clone())], + )?; let effective = native( py, - "resolve_model_full_refresh", + NativeAttr::ResolveModelFullRefresh, &[ ("model", model.clone()), ("cli_full_refresh", boolean(py, full_refresh)?), @@ -65,7 +70,7 @@ fn display_entry<'py>( }; planner_model( py, - "ModelPlanEntry", + RecordId::ModelPlanEntry, &[ ("key", model.getattr("key")?), ("name", model.getattr("name")?), @@ -74,7 +79,7 @@ fn display_entry<'py>( "action", planner_type( py, - "PlanAction", + RecordId::PlanAction, display_action(&materialization_type, effective)?, )?, ), @@ -83,7 +88,7 @@ fn display_entry<'py>( "reason", planner_type( py, - "PlanReason", + RecordId::PlanReason, if effective { "FULL_REFRESH" } else { @@ -106,12 +111,12 @@ fn display_entry<'py>( "backfill", native( py, - "BackfillResult", + RecordId::BackfillResult, &[( "action", planner_type( py, - "BackfillAction", + RecordId::BackfillAction, if effective { "FULL" } else { "FORWARD_ONLY" }, )?, )], @@ -149,11 +154,11 @@ fn build_display_only_sqlbuild_plan<'py>( .map(|entry| entry.getattr("key")) .collect::>()?; let upstream = - imported(py, NATIVE_MODULE, "build_execution_upstream_deps")?.call1((project,))?; - let downstream = imported(py, NATIVE_MODULE, "build_downstream_deps")?.call1((&upstream,))?; + registry::native(py, NativeAttr::BuildExecutionUpstreamDeps)?.call1((project,))?; + let downstream = registry::native(py, NativeAttr::BuildDownstreamDeps)?.call1((&upstream,))?; planner_model( py, - "PlanOutput", + RecordId::PlanOutput, &[ ("execution_order", PyTuple::new(py, &keys)?.into_any()), ("model_entries", PyTuple::new(py, entries)?.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/execution_plan.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/execution_plan.rs index ee974e5a4e..6a8888bc25 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/execution_plan.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/execution_plan.rs @@ -3,19 +3,20 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, within, }; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::native_threads::exit_signal::signal_interpreter_exit; use crate::bindings::_helpers::planner_entries::values::{ COMPILE_TYPES, boolean, native, replaced, string, }; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; use std::time::Instant; -const INSPECTION_CATALOG: (&str, &str) = ( - "sqlbuild.adapter.relations.main.open_inspection_catalog", - "open_inspection_catalog", -); +const INSPECTION_CATALOG: AdapterHost = AdapterHost::OpenInspectionCatalog; const TEST_PLANNING_THREAD: &str = "sqlbuild-test-planning"; /// How long interpreter exit waits for each SQL test planning thread still running native code. const PLANNING_EXIT_JOIN_SECONDS: f64 = 5.0; @@ -62,7 +63,7 @@ fn sql_test_planning<'py>( "set_result", (native( py, - "PlannedSqlTests", + RecordId::PlannedSqlTests, &[ ("selected_keys", selected_keys.clone()), ("sql_test_selection", sql_test_selection.clone()), @@ -82,7 +83,7 @@ fn sql_test_planning<'py>( &[ ( "target", - imported(py, "sqlbuild._native", "plan_selected_sql_tests_into")?, + registry::native(py, NativeAttr::PlanSelectedSqlTestsInto)?, ), ("kwargs", kwargs.into_any()), ("name", string(py, TEST_PLANNING_THREAD)), @@ -101,7 +102,7 @@ fn scopes_and_cursor_overrides<'py>( let py = project.py(); let scopes = native( py, - "resolve_planner_scopes", + NativeAttr::ResolvePlannerScopes, &[ ("project", project.clone()), ("selection", args.value("selection")?), @@ -122,7 +123,7 @@ fn scopes_and_cursor_overrides<'py>( let overrides = args.value("overrides")?; let typed = native( py, - "type_cursor_overrides", + NativeAttr::TypeCursorOverrides, &[ ("cursor_overrides", overrides.getattr("cursor_overrides")?), ("selected_models", PyTuple::new(py, models)?.into_any()), @@ -145,7 +146,7 @@ fn change_results<'py>( let versions = identities.getattr("version_identities")?; let changes = native( py, - "detect_changes", + NativeAttr::DetectChanges, &[ ("project", project.clone()), ("scope", scopes.getattr("inspection_scope")?), @@ -164,7 +165,7 @@ fn change_results<'py>( if !policies.getattr("selection_diagnostics")?.is_truthy()? { let empty = native( py, - "PlannerChangeResults", + RecordId::PlannerChangeResults, &[ ("models", PyDict::new(py).into_any()), ("functions", PyDict::new(py).into_any()), @@ -174,7 +175,7 @@ fn change_results<'py>( } let stale = native( py, - "detect_stale_warning_changes", + NativeAttr::DetectStaleWarningChanges, &[ ("project", project.clone()), ("scopes", scopes.clone()), @@ -199,7 +200,7 @@ fn planned_output<'py>( let policies = args.value("policies")?; let warehouse = native( py, - "gather_planner_warehouse_state", + NativeAttr::GatherPlannerWarehouseState, &[ ("runtime", runtime.clone()), ("scopes", scopes.clone()), @@ -210,7 +211,7 @@ fn planned_output<'py>( let started = Instant::now(); let identities = native( py, - "build_planner_identity_context", + NativeAttr::BuildPlannerIdentityContext, &[ ("project", project.clone()), ("scopes", scopes.clone()), @@ -222,7 +223,7 @@ fn planned_output<'py>( )?; native( py, - "check_selected_scope_buildability", + NativeAttr::CheckSelectedScopeBuildability, &[ ("runtime", runtime.clone()), ("scopes", scopes.clone()), @@ -240,7 +241,7 @@ fn planned_output<'py>( let inspection_scope = scopes.getattr("inspection_scope")?; let resolved_actions = native( py, - "resolve_model_actions", + NativeAttr::ResolveModelActions, &[ ("scope", inspection_scope.clone()), ("changes", changes.clone()), @@ -248,7 +249,7 @@ fn planned_output<'py>( )?; let source_freshness = native( py, - "build_planner_source_freshness_result", + NativeAttr::BuildPlannerSourceFreshnessResult, &[ ("project", project.clone()), ("adapter", args.value("adapter")?), @@ -263,7 +264,7 @@ fn planned_output<'py>( )?; let pruning = native( py, - "prune_planner_execution_scope", + NativeAttr::PrunePlannerExecutionScope, &[ ("scopes", scopes.clone()), ("resolved_actions", resolved_actions), @@ -271,7 +272,7 @@ fn planned_output<'py>( )?; let reconciliation = native( py, - "reconcile_execution_changes", + NativeAttr::ReconcileExecutionChanges, &[ ("warehouse", warehouse.clone()), ("identities", identities.clone()), @@ -281,7 +282,7 @@ fn planned_output<'py>( )?; let entries = native( py, - "build_planner_entry_results", + NativeAttr::BuildPlannerEntryResults, &[ ("runtime", runtime.clone()), ("warehouse", warehouse.clone()), @@ -297,7 +298,7 @@ fn planned_output<'py>( let planned_sql_tests = test_planning.call_method0("result")?; let mut plan_output = native( py, - "assemble_base_plan_output", + NativeAttr::AssembleBasePlanOutput, &[ ("runtime", runtime.clone()), ("warehouse", warehouse.clone()), @@ -312,7 +313,7 @@ fn planned_output<'py>( )?; plan_output = native( py, - "with_storage_policies", + NativeAttr::WithStoragePolicies, &[ ("plan_output", plan_output), ("runtime", runtime.clone()), @@ -323,7 +324,7 @@ fn planned_output<'py>( )?; plan_output = native( py, - "with_plan_warnings", + NativeAttr::WithPlanWarnings, &[ ("runtime", runtime.clone()), ("scopes", scopes.clone()), @@ -338,7 +339,7 @@ fn planned_output<'py>( )?; plan_output = native( py, - "with_plan_metadata", + NativeAttr::WithPlanMetadata, &[ ("plan_output", plan_output), ("pruning", pruning), @@ -385,7 +386,7 @@ fn build_execution_plan<'py>( let connection = args.value("connection")?; let runtime = native( py, - "PlannerRuntime", + RecordId::PlannerRuntime, &[ ("project", project.clone()), ("adapter", adapter.clone()), @@ -409,7 +410,7 @@ fn build_execution_plan<'py>( )?; let planned = (|| { let catalog = call_with( - &imported(py, INSPECTION_CATALOG.0, INSPECTION_CATALOG.1)?, + &host::entry(py, INSPECTION_CATALOG)?, &[("adapter", adapter), ("connection", connection)], )?; within(&catalog, |_| { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/model_entries.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/model_entries.rs index 2b7025516e..65a5a8a122 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/model_entries.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/model_entries.rs @@ -2,14 +2,16 @@ use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported}; use crate::bindings::_helpers::planner_entries::values::{ - COMPILE_EXCEPTIONS, COMPILE_TYPES, MICROBATCH_CONCURRENCY_MESSAGE, PLANNER_EXCEPTIONS, - PLANNER_TYPES, boolean, error_of, native, none, planner_type, replaced, string, + COMPILE_TYPES, MICROBATCH_CONCURRENCY_MESSAGE, boolean, error_of, native, none, planner_type, + replaced, string, }; use crate::bindings::_helpers::planner_resolution::cursor_ranges::{ CursorWindowInputs, config_str, cursor_safety_configs, empty_cursor_inputs, microbatch_range, model_cursor_overrides, plan_cursor_bounds, }; use crate::bindings::_helpers::planner_resolution::cursor_warnings::with_cursor_safety_warnings; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; @@ -41,8 +43,11 @@ fn plan_model_from_change<'py>( let backfill = if request.full_refresh { native( py, - "BackfillResult", - &[("action", planner_type(py, "BackfillAction", "FULL")?)], + RecordId::BackfillResult, + &[( + "action", + planner_type(py, RecordId::BackfillAction, "FULL")?, + )], )? } else { request.change_result.getattr("backfill")? @@ -50,7 +55,7 @@ fn plan_model_from_change<'py>( let cursor_column = config_str(model, "cursor")?; native( py, - "validate_source_cursor_input_columns", + NativeAttr::ValidateSourceCursorInputColumns, &[ ("model", model.clone()), ("cursor_column", cursor_column.clone()), @@ -65,7 +70,7 @@ fn plan_model_from_change<'py>( )?; let resolved = native( py, - "resolve_model_plan_action", + NativeAttr::ResolveModelPlanAction, &[ ("model", model.clone()), ("change_result", request.change_result.clone()), @@ -73,10 +78,14 @@ fn plan_model_from_change<'py>( ], )?; let (action, reason) = (resolved.get_item(0)?, resolved.get_item(1)?); - let materialization_type = native(py, "get_materialization_type", &[("model", model.clone())])?; + let materialization_type = native( + py, + NativeAttr::GetMaterializationType, + &[("model", model.clone())], + )?; let replaces_relation = native( py, - "replaces_incremental_relation", + NativeAttr::ReplacesIncrementalRelation, &[ ("materialization_type", materialization_type.clone()), ("action", action.clone()), @@ -86,7 +95,7 @@ fn plan_model_from_change<'py>( let full_rebuild_cause = if replaces_relation { native( py, - "describe_full_rebuild_cause", + NativeAttr::DescribeFullRebuildCause, &[ ("change_result", request.change_result.clone()), ("full_refresh", full_refresh.clone()), @@ -98,7 +107,7 @@ fn plan_model_from_change<'py>( if !full_rebuild_cause.is_none() && request.protect_full_refresh && !request.full_refresh { native( py, - "check_full_rebuild_allowed", + NativeAttr::CheckFullRebuildAllowed, &[ ("model", model.clone()), ("change_result", request.change_result.clone()), @@ -108,7 +117,7 @@ fn plan_model_from_change<'py>( } let pair = native( py, - "CursorOverridePair", + RecordId::CursorOverridePair, &[ ("start_cursor_override", request.overrides.0.clone()), ("end_cursor_override", request.overrides.1.clone()), @@ -116,7 +125,7 @@ fn plan_model_from_change<'py>( )?; let resolved_sql = native( py, - "resolve_model_sql", + NativeAttr::ResolveModelSql, &[ ("adapter", request.adapter.clone()), ("model", model.clone()), @@ -135,7 +144,7 @@ fn plan_model_from_change<'py>( )?; let hooks = native( py, - "resolve_model_hook_entries", + NativeAttr::ResolveModelHookEntries, &[ ("model", model.clone()), ("adapter", request.adapter.clone()), @@ -149,7 +158,7 @@ fn plan_model_from_change<'py>( let (future_config, start_config) = cursor_safety_configs(model, context)?; native( py, - "validate_capped_watermark_inputs", + NativeAttr::ValidateCappedWatermarkInputs, &[ ("model", model.clone()), ("models_by_name", context.getattr("models_by_name")?), @@ -158,7 +167,7 @@ fn plan_model_from_change<'py>( )?; let cursor_input_relations = native( py, - "build_cursor_input_relations", + NativeAttr::BuildCursorInputRelations, &[ ("model", model.clone()), ("adapter", request.adapter.clone()), @@ -199,7 +208,7 @@ fn plan_model_from_change<'py>( let range = microbatch_range(model, &window)?; let assembled = native( py, - "assemble_model_plan_entry", + NativeAttr::AssembleModelPlanEntry, &[ ("model", model.clone()), ("adapter", request.adapter.clone()), @@ -260,7 +269,7 @@ fn check_binding_diagnostics<'py>( } let bound = native( py, - "source_binding_diagnostics", + NativeAttr::SourceBindingDiagnostics, &[ ("project", project.clone()), ("columns", relations.getattr("source_warehouse_columns")?), @@ -301,7 +310,7 @@ fn check_binding_diagnostics<'py>( } Err(error_of( py, - (PLANNER_EXCEPTIONS, "PlannerInputError"), + ErrorId::PlannerInput, &lines.join("\n"), &[("code", first.getattr("code")?)], )) @@ -322,8 +331,8 @@ fn check_microbatch_concurrency<'py>( entries: &[Bound<'py, PyAny>], ) -> PyResult>> { let py = project.py(); - let skip = planner_type(py, "PlanAction", "SKIP")?; - let create = planner_type(py, "PlanAction", "CREATE_TABLE")?; + let skip = planner_type(py, RecordId::PlanAction, "SKIP")?; + let create = planner_type(py, RecordId::PlanAction, "CREATE_TABLE")?; let mut configured: Vec> = Vec::new(); for entry in entries { if entry.getattr("batch_concurrency")?.gt(1)? && entry.getattr("action")?.ne(&skip)? { @@ -332,11 +341,11 @@ fn check_microbatch_concurrency<'py>( } let settings = project.getattr("settings")?; if !configured.is_empty() && !settings.getattr("microbatch_concurrency")?.is_truthy()? { - let guidance = native(py, "microbatch_guidance", &[])?; + let guidance = native(py, NativeAttr::MicrobatchGuidance, &[])?; let prefix: &str = MICROBATCH_CONCURRENCY_MESSAGE; return Err(error_of( py, - (COMPILE_EXCEPTIONS, "CompileInputError"), + ErrorId::CompileInput, &format!( "{prefix}{}; {}", names_of(&configured)?, @@ -352,7 +361,7 @@ fn check_microbatch_concurrency<'py>( { return Err(error_of( py, - (COMPILE_EXCEPTIONS, "CompileInputError"), + ErrorId::CompileInput, &format!( "adapter '{}' does not support concurrent microbatch delete/insert DML; set \ batch_concurrency = 1", @@ -373,12 +382,12 @@ fn check_microbatch_concurrency<'py>( let count = concurrent.len(); native( py, - "PlanWarning", + RecordId::PlanWarning, &[ ("model_name", none(py)), ( "severity", - imported(py, "sqlbuild._native", "WarningSeverity")?.getattr("WARNING")?, + registry::class(py, RecordId::WarningSeverity)?.getattr("WARNING")?, ), ("code", string(py, MICROBATCH_COMPLEXITY_CODE)), ( @@ -417,7 +426,7 @@ fn plan_entries<'py>(args: &KeywordArguments<'py>) -> PyResult let full_refresh = args.value("full_refresh")?; let inputs = args.value("build_inputs")?; let inputs = if inputs.is_none() { - native(py, "PlanEntryBuildInputs", &[])? + native(py, RecordId::PlanEntryBuildInputs, &[])? } else { inputs }; @@ -429,7 +438,7 @@ fn plan_entries<'py>(args: &KeywordArguments<'py>) -> PyResult let end_override = inputs.getattr("end_cursor_override")?; let producers = native( py, - "runtime_cursor_producer_names", + NativeAttr::RuntimeCursorProducerNames, &[ ("scope", scope.clone()), ("source_map", relations.getattr("source_map")?), @@ -444,7 +453,7 @@ fn plan_entries<'py>(args: &KeywordArguments<'py>) -> PyResult let models_by_name = scope.getattr("models_by_name")?; let context = native( py, - "ModelPlanContext", + RecordId::ModelPlanContext, &[ ("model_locations", relations.getattr("model_locations")?), ("models_by_name", models_by_name.clone()), @@ -475,8 +484,8 @@ fn plan_entries<'py>(args: &KeywordArguments<'py>) -> PyResult ("invocation_time", inputs.getattr("invocation_time")?), ], )?; - let skip = planner_type(py, "PlanAction", "SKIP")?; - let microbatch = imported(py, PLANNER_TYPES, "IncrementalMode")?.getattr("MICROBATCH")?; + let skip = planner_type(py, RecordId::PlanAction, "SKIP")?; + let microbatch = registry::class(py, RecordId::IncrementalMode)?.getattr("MICROBATCH")?; let mut entries: Vec> = Vec::new(); let mut warnings: Vec> = Vec::new(); for key in scope.getattr("execution_order")?.try_iter()? { @@ -497,7 +506,7 @@ fn plan_entries<'py>(args: &KeywordArguments<'py>) -> PyResult )?; let effective_full_refresh = native( py, - "resolve_model_full_refresh", + NativeAttr::ResolveModelFullRefresh, &[ ("model", model.clone()), ("cli_full_refresh", full_refresh.clone()), @@ -525,7 +534,7 @@ fn plan_entries<'py>(args: &KeywordArguments<'py>) -> PyResult ("action", skip.clone()), ( "reason", - planner_type(py, "PlanReason", "SOURCE_FRESHNESS_ERROR")?, + planner_type(py, RecordId::PlanReason, "SOURCE_FRESHNESS_ERROR")?, ), ], )?; @@ -537,7 +546,7 @@ fn plan_entries<'py>(args: &KeywordArguments<'py>) -> PyResult ("action", skip.clone()), ( "reason", - planner_type(py, "PlanReason", "EXTERNAL_UPSTREAM_FAILED")?, + planner_type(py, RecordId::PlanReason, "EXTERNAL_UPSTREAM_FAILED")?, ), ], )?; @@ -553,7 +562,7 @@ fn plan_entries<'py>(args: &KeywordArguments<'py>) -> PyResult if entry.getattr("action")?.ne(&skip)? { let limited = native( py, - "apply_configured_microbatch_limits", + NativeAttr::ApplyConfiguredMicrobatchLimits, &[("entry", entry), ("inputs", inputs.clone())], )?; entry = limited.get_item(0)?; @@ -572,7 +581,7 @@ fn plan_entries<'py>(args: &KeywordArguments<'py>) -> PyResult } native( py, - "PlannerModelEntryResults", + RecordId::PlannerModelEntryResults, &[ ("entries", PyTuple::new(py, entries)?.into_any()), ("warnings", PyTuple::new(py, warnings)?.into_any()), @@ -637,7 +646,7 @@ fn plan_model<'py>( let snapshot = args.value("snapshot")?; let change_result = native( py, - "detect_model_changes", + NativeAttr::DetectModelChanges, &[ ("model", model.clone()), ("snapshot", snapshot.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/plan_output.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/plan_output.rs index e8715b7dda..a308ef4b52 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/plan_output.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/plan_output.rs @@ -1,11 +1,12 @@ //! `PlanOutput` assembly from planned model entries, seeds, sources, functions, audits and tests. -use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; use crate::bindings::_helpers::planner_entries::model_entries::scope_overlaps; use crate::bindings::_helpers::planner_entries::values::{ - DISCOVERY_MODELS, PLANNER_EXCEPTIONS, error_of, native, none, planner_model, planner_type, - string, + error_of, native, none, planner_model, planner_type, string, }; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyFrozenSet, PyList, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; @@ -66,23 +67,23 @@ fn seed_plan_reason<'py>( .getattr("existing_relations")? .contains(seed_name)? { - return planner_type(py, "PlanReason", "FIRST_RUN"); + return planner_type(py, RecordId::PlanReason, "FIRST_RUN"); } let fingerprint = snapshot .getattr("fingerprints")? .getattr("seeds")? .call_method1("get", (seed_name,))?; if fingerprint.is_none() { - return planner_type(py, "PlanReason", "FIRST_RUN"); + return planner_type(py, RecordId::PlanReason, "FIRST_RUN"); } if !expected_version_hash.is_none() && fingerprint .getattr("version_hash")? .ne(expected_version_hash)? { - return planner_type(py, "PlanReason", "CONFIG_CHANGED"); + return planner_type(py, RecordId::PlanReason, "CONFIG_CHANGED"); } - planner_type(py, "PlanReason", "NO_CHANGE") + planner_type(py, RecordId::PlanReason, "NO_CHANGE") } fn seed_entries<'py>( @@ -104,7 +105,7 @@ fn seed_entries<'py>( let name = seed.getattr("name")?; entries.push(planner_model( py, - "SeedPlanEntry", + RecordId::SeedPlanEntry, &[ ("key", seed.getattr("key")?), ("name", name.clone()), @@ -115,7 +116,11 @@ fn seed_entries<'py>( ), ( "columns", - native(py, "extract_seed_columns", &[("seed", seed.clone())])?, + native( + py, + NativeAttr::ExtractSeedColumns, + &[("seed", seed.clone())], + )?, ), ( "csv_settings", @@ -159,7 +164,7 @@ fn provider_usages<'py>( let usage = usage?; usages.push(native( py, - "PlanProviderUsage", + RecordId::PlanProviderUsage, &[ ("provider_name", usage.getattr("provider_name")?), ("consumer_kind", string(py, consumer_kind)), @@ -182,7 +187,7 @@ fn hook_provider_usages<'py>( ) -> PyResult>> { let py = entry.py(); let mut usages: Vec> = Vec::new(); - let python_hook = imported(py, DISCOVERY_MODELS, "PythonHookEntry")?; + let python_hook = registry::class(py, RecordId::PythonHookEntry)?; let mut hooks: Vec> = Vec::new(); for field in ["pre_hooks", "post_hooks"] { let hook_entry = entry.getattr(field)?; @@ -282,7 +287,7 @@ fn require_intermediate_loader_targets<'py>( }; let dependencies = native( py, - "upstream_loader_dependency_names", + NativeAttr::UpstreamLoaderDependencyNames, &[ ("loader_function", loader_function), ("loader_functions", loader_functions.clone()), @@ -306,7 +311,7 @@ fn require_intermediate_loader_targets<'py>( let source_name = source_entry.getattr("name")?.str()?; return Err(error_of( py, - (PLANNER_EXCEPTIONS, "PlannerInputError"), + ErrorId::PlannerInput, &format!( "Source '{source_name}' requires intermediate loader '{}', but its target \ relation '{}' does not exist; use +source:{source_name} to refresh upstream \ @@ -335,7 +340,7 @@ pub(super) fn function_entry<'py>( let py = function.py(); let body_sql = native( py, - "resolve_function_sql", + NativeAttr::ResolveFunctionSql, &[ ("adapter", adapter.clone()), ("function", function.clone()), @@ -359,7 +364,7 @@ pub(super) fn function_entry<'py>( let field = |name: &str| function.getattr(name); planner_model( py, - "FunctionPlanEntry", + RecordId::FunctionPlanEntry, &[ ("key", field("key")?), ("name", field("name")?), @@ -471,7 +476,7 @@ pub(super) fn selected_test_entries<'py>( let fixture_context = if analysis.is_truthy()? { native( py, - "build_relation_fixture_context", + NativeAttr::BuildRelationFixtureContext, &[("project", project.clone())], )? } else { @@ -479,7 +484,7 @@ pub(super) fn selected_test_entries<'py>( }; let results = native( py, - "plan_sql_tests", + NativeAttr::PlanSqlTests, &[ ("tests", tuple_of(py, selected)?), ("project", project.clone()), @@ -510,7 +515,7 @@ pub(super) fn selected_test_entries<'py>( if !diagnostics.is_empty() { return Err(error_of( py, - (PLANNER_EXCEPTIONS, "PlannerInputError"), + ErrorId::PlannerInput, &format!("Invalid SQL test fixtures:\n- {}", diagnostics.join("\n- ")), &[], )); @@ -529,7 +534,7 @@ fn planned_sql_tests<'py>( let selected = selected_test_entries((project, adapter), selected_keys, selection, &none(py))?; native( py, - "PlannedSqlTests", + RecordId::PlannedSqlTests, &[ ("selected_keys", selected_keys.clone()), ("sql_test_selection", selection.clone()), @@ -549,7 +554,7 @@ fn plan_output<'py>(args: &KeywordArguments<'py>) -> PyResult> let model_entry_results = args.value("model_entry_results")?; let extras = args.value("extras")?; let extras = if extras.is_none() { - native(py, "PlanOutputExtras", &[])? + native(py, RecordId::PlanOutputExtras, &[])? } else { extras }; @@ -559,7 +564,7 @@ fn plan_output<'py>(args: &KeywordArguments<'py>) -> PyResult> let seeds = seed_entries(&project, &snapshot, &selected_keys, &extras)?; let source_load_entries = native( py, - "build_source_load_entries", + NativeAttr::BuildSourceLoadEntries, &[ ("execution_order", execution_order.clone()), ("selected_keys", selected_keys.clone()), @@ -585,7 +590,7 @@ fn plan_output<'py>(args: &KeywordArguments<'py>) -> PyResult> } let audit_entries = native( py, - "build_selected_audit_entries", + NativeAttr::BuildSelectedAuditEntries, &[ ("project", project.clone()), ("adapter", adapter.clone()), @@ -624,7 +629,7 @@ fn plan_output<'py>(args: &KeywordArguments<'py>) -> PyResult> } planner_model( py, - "PlanOutput", + RecordId::PlanOutput, &[ ("execution_order", tuple_of(py, scoped_order)?), ("model_entries", model_entries.clone()), @@ -730,7 +735,7 @@ fn plan_function<'py>( let reason = if args.passed("reason")? { args.value("reason")? } else { - planner_type(py, "PlanReason", "NO_CHANGE")? + planner_type(py, RecordId::PlanReason, "NO_CHANGE")? }; function_entry( &args.value("function")?, @@ -781,7 +786,7 @@ fn plan_selected_sql_tests_into<'py>( let future = args.value("future")?; match native( py, - "plan_selected_sql_tests", + NativeAttr::PlanSelectedSqlTests, &[ ("project", args.value("project")?), ("adapter", args.value("adapter")?), @@ -805,7 +810,7 @@ fn sql_test_model_chain_names_by_key<'py>( let py = project.py(); let chains = native( py, - "resolve_sql_test_model_chains", + NativeAttr::ResolveSqlTestModelChains, &[("project", project.clone()), ("tests", tests.clone())], )?; let by_key = PyDict::new(py); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/relations.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/relations.rs index fd974efa8a..a81b416a48 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/relations.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/relations.rs @@ -2,6 +2,7 @@ use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; use crate::bindings::_helpers::planner_entries::values::{native, none, planner_model}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyFrozenSet, PySet, PySetMethods, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; @@ -17,7 +18,7 @@ fn selected_audit_relation_keys<'py>( let audit = audit?; if native( py, - "audit_is_selected", + NativeAttr::AuditIsSelected, &[ ("audit", audit.clone()), ("selected_keys", selected_keys.clone()), @@ -60,7 +61,7 @@ fn source_warehouse_columns<'py>( )?; let gathered = native( py, - "gather_source_inspection", + NativeAttr::GatherSourceInspection, &[ ("project", project.clone()), ("adapter", adapter.clone()), @@ -82,7 +83,7 @@ pub(crate) fn planner_relations_context<'py>( let scope = args.value("scope")?; let deferral = args.value("deferral")?; let deferral = if deferral.is_none() { - planner_model(py, "DeferralInputs", &[])? + planner_model(py, RecordId::DeferralInputs, &[])? } else { deferral }; @@ -91,24 +92,24 @@ pub(crate) fn planner_relations_context<'py>( let selected_keys = scope.getattr("selected_keys")?; let mut model_locations = native( py, - "build_model_locations", + NativeAttr::BuildModelLocations, &[("models", project.getattr("models")?)], )?; let mut seed_locations = native( py, - "build_seed_locations", + NativeAttr::BuildSeedLocations, &[("seeds", project.getattr("seeds")?)], )?; let function_locations = native( py, - "build_function_locations", + NativeAttr::BuildFunctionLocations, &[("functions", project.getattr("functions")?)], )?; let deferred_locations = deferral.getattr("deferred_locations")?; if !deferred_locations.is_none() { let applied = native( py, - "apply_deferred_locations", + NativeAttr::ApplyDeferredLocations, &[ ("model_locations", model_locations), ("seed_locations", seed_locations), @@ -128,7 +129,7 @@ pub(crate) fn planner_relations_context<'py>( }; let source_map = native( py, - "build_source_load_map", + NativeAttr::BuildSourceLoadMap, &[ ("project", project.clone()), ("selected_keys", load_keys), @@ -158,12 +159,12 @@ pub(crate) fn planner_relations_context<'py>( if let Some(names) = names { pairs.push(("read_source_names", names)); } - native(py, "build_source_read_map", &pairs) + native(py, NativeAttr::BuildSourceReadMap, &pairs) }; let source_read_map = read_map(&source_map, None)?; let python_source_map = native( py, - "with_declared_source_reads", + NativeAttr::WithDeclaredSourceReads, &[ ("project", project.clone()), ("source_map", source_map.clone()), @@ -185,7 +186,7 @@ pub(crate) fn planner_relations_context<'py>( )?; let semantic_columns = native( py, - "get_semantic_source_columns", + NativeAttr::GetSemanticSourceColumns, &[ ("project", project.clone()), ("adapter", adapter.clone()), @@ -197,7 +198,7 @@ pub(crate) fn planner_relations_context<'py>( )?; planner_model( py, - "PlannerRelationsContext", + RecordId::PlannerRelationsContext, &[ ("model_locations", model_locations), ("seed_locations", seed_locations), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/values.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/values.rs index f35a07d9ae..0bdb855ac3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/values.rs @@ -1,47 +1,41 @@ //! Python boundary values shared by plan entry and plan output assembly. use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class_at; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeItem, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::IntoPyObject; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python}; use pyo3::types::PyString; -pub(super) const NATIVE_MODULE: &str = "sqlbuild._native"; -pub(super) const PLANNER_MODELS: &str = "sqlbuild._native"; -pub(super) const PLANNER_TYPES: &str = "sqlbuild._native"; -pub(super) const PLANNER_EXCEPTIONS: &str = "sqlbuild._native"; pub(super) const MICROBATCH_CONCURRENCY_MESSAGE: &str = "batch_concurrency > 1 requires concurrent microbatches; selected model(s): "; -pub(super) const COMPILE_EXCEPTIONS: &str = "sqlbuild._native"; pub(super) const COMPILE_TYPES: &str = "sqlbuild.compiler.compile.types"; -pub(super) const COMPILE_MODELS: &str = "sqlbuild._native"; -pub(super) const DISCOVERY_MODELS: &str = "sqlbuild._native"; /// `sqlbuild._native.name(**pairs)`. pub(super) fn native<'py>( py: Python<'py>, - name: &str, + name: impl Into, pairs: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - call_with(&imported(py, NATIVE_MODULE, name)?, pairs) + call_with(®istry::item(py, name)?, pairs) } /// The planner record `name(**pairs)`. pub(super) fn planner_model<'py>( py: Python<'py>, - name: &str, + name: RecordId, pairs: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - call_with(&shape_class_at(py, PLANNER_MODELS, name)?, pairs) + call_with(®istry::class(py, name)?, pairs) } /// One member of a planner enumeration. pub(super) fn planner_type<'py>( py: Python<'py>, - class: &str, + class: RecordId, member: &str, ) -> PyResult> { - imported(py, PLANNER_TYPES, class)?.getattr(member) + registry::member(py, class, member) } /// A Python string. @@ -71,11 +65,11 @@ pub(super) fn replaced<'py>( /// `class(message, **fields)` from `module`, as a raised error. pub(super) fn error_of( py: Python<'_>, - (module, class): (&str, &str), + class: ErrorId, message: &str, fields: &[(&str, Bound<'_, PyAny>)], ) -> PyErr { - match imported(py, module, class) + match registry::error(py, class) .and_then(|class| class.call((message,), Some(&keywords(py, fields)?))) { Ok(error) => PyErr::from_value(error), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/graph.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/graph.rs index bc34edda00..2b47455ce3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/graph.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/graph.rs @@ -22,14 +22,13 @@ use sqlbuild_analysis::planner_identity::models::{AvailableUpstreams, GraphKey, use crate::bindings::_helpers::analysis::explicit_references::{ GateRow, HookRow, native_execution_gate_edges, }; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::graph::algorithms::{closure, inverted}; use crate::bindings::_helpers::graph::keys::KeyCache; use crate::bindings::_helpers::planner_identity::hashing::graph_key; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; -const EXCEPTIONS_MODULE: &str = "sqlbuild._native"; const TABLE_FN_TEST_MODE: &str = "table_fn"; const INLINE_SQL_HOOK: &str = "inline SQL"; @@ -241,9 +240,7 @@ fn build_downstream_deps<'py>(upstream: &Bound<'py, PyAny>) -> PyResult, message: String) -> PyErr { - match imported(py, EXCEPTIONS_MODULE, "PlannerInputError") - .and_then(|class| class.call1((message,))) - { + match registry::error(py, ErrorId::PlannerInput).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } @@ -369,7 +366,7 @@ fn check_buildability<'py>( Some(name_set(&deferred_relations)?) }, }; - let entry_class: Bound<'py, PyAny> = imported(py, "sqlbuild._native", "MissingUpstream")?; + let entry_class: Bound<'py, PyAny> = registry::class(py, RecordId::MissingUpstream)?; let mut entries: Vec> = Vec::new(); for missing in missing_upstreams(&selected, &deps, &available) { entries.push(call_with( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/hashing.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/hashing.rs index 65d24bc6d4..ca508ec8b1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/hashing.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/hashing.rs @@ -13,11 +13,11 @@ use sqlbuild_analysis::sql_tokens::main::definition_fingerprint::definition_fing use sqlbuild_core::panics::main::is_compiler_panic::is_compiler_panic; use crate::bindings::_helpers::boundary::panics::compiler_error; -use crate::bindings::_helpers::compile_driver::python_calls::{imported, keywords}; +use crate::bindings::_helpers::compile_driver::python_calls::keywords; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; use crate::bindings::types::CompilerDetach; -const EXCEPTIONS_MODULE: &str = "sqlbuild._native"; - /// The query fingerprint of a model, or the planner input error naming the model. pub(crate) fn model_query_hash( py: Python<'_>, @@ -43,8 +43,8 @@ fn fingerprint_error(py: Python<'_>, subject: &str, message: String) -> PyErr { return compiler_error(message); } let text = |value: &str| PyString::new(py, value).into_any(); - let raised: PyResult> = imported(py, EXCEPTIONS_MODULE, "PlannerInputError") - .and_then(|class| { + let raised: PyResult> = + registry::error(py, ErrorId::PlannerInput).and_then(|class| { class.call( (format!( "{subject} cannot be fingerprinted for change detection: {message}" diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/identities.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/identities.rs index 63bf9d947f..4b795d067c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/identities.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/identities.rs @@ -17,12 +17,15 @@ use sqlbuild_analysis::planner_identity::models::{GraphKey, IdentityGraph, Ident use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, }; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; use crate::bindings::_helpers::planner_identity::hashing::{graph_key, model_query_hash}; use crate::bindings::_helpers::planner_identity::metadata::{ function_local_hashes, model_version_identity_metadata_json, seed_identity, }; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const HOOK_IDENTITIES_MODULE: &str = "sqlbuild.compiler.python_nodes.main._hook_identities"; const DATACLASSES_MODULE: &str = "dataclasses"; fn key_of(kind: &str, name: &str) -> GraphKey { @@ -49,7 +52,7 @@ fn hook_version_hashes<'py>(hook_functions: &Bound<'py, PyAny>) -> PyResult = hook_functions.py(); let hashes: Bound<'py, PyDict> = PyDict::new(py); let identities: Bound<'py, PyAny> = - imported(py, HOOK_IDENTITIES_MODULE, "build_hook_identities")?.call1((hook_functions,))?; + host::entry(py, UserCodeHost::BuildHookIdentities)?.call1((hook_functions,))?; for item in identities.call_method0("items")?.try_iter()? { let (name, identity): (Bound<'py, PyAny>, Bound<'py, PyAny>) = item?.extract()?; hashes.set_item(name, identity.getattr("version_hash")?)?; @@ -182,7 +185,7 @@ fn build_direct_model_version_identities<'py>( } } call_with( - &imported(py, "sqlbuild._native", "DirectModelVersionIdentities")?, + ®istry::class(py, RecordId::DirectModelVersionIdentities)?, &[ ("function_local_hashes", function_hashes.into_any()), ("seed_version_hashes", seed_hashes.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/staleness.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/staleness.rs index f01cb48fc3..5559b2c1e6 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/staleness.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/staleness.rs @@ -16,11 +16,11 @@ use sqlbuild_analysis::planner_identity::models::{ GraphKey, StaleWarning, StalenessGraph, StalenessNames, }; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::planner_changes::detect::{ModelPass, model_change}; use crate::bindings::_helpers::planner_identity::hashing::graph_key; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; /// Change kinds that mean the model's own identity changed. const OWN_CHANGE_KINDS: [&str; 5] = [ @@ -143,8 +143,8 @@ fn plan_warnings<'py>( py: Python<'py>, warnings: Vec, ) -> PyResult> { - let warning_class: Bound<'py, PyAny> = imported(py, "sqlbuild._native", "PlanWarning")?; - let severity: Bound<'py, PyAny> = native_enum(py, "WarningSeverity", "warning")?; + let warning_class: Bound<'py, PyAny> = registry::class(py, RecordId::PlanWarning)?; + let severity: Bound<'py, PyAny> = native_enum(py, RecordId::WarningSeverity, "warning")?; let mut rendered: Vec> = Vec::with_capacity(warnings.len()); for warning in warnings { let message: String = stale_upstream_warning_message( @@ -258,7 +258,7 @@ fn classify_selection_staleness_warnings<'py>( }, }; let warning_class: Bound<'py, PyAny> = - imported(py, "sqlbuild._native", "SelectionStalenessWarning")?; + registry::class(py, RecordId::SelectionStalenessWarning)?; let mut warnings: Vec> = Vec::new(); for warning in classify_selection_staleness(&staleness) { warnings.push(call_with( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_candidates.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_candidates.rs index 27a15d694e..b209ee41a1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_candidates.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_candidates.rs @@ -5,6 +5,7 @@ use crate::bindings::_helpers::planner_migrations::column_objects::{ config, definition_hash, lower, native, }; use crate::bindings::_helpers::planner_migrations::models::ColumnCandidate; +use crate::bindings::_helpers::records::ids::NativeAttr; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::types::{PyDict, PyDictMethods, PyFrozenSet, PyTuple}; @@ -72,7 +73,7 @@ pub(super) fn candidates<'py>( let previous = if changed { native( py, - "parse_query_shape", + NativeAttr::ParseQueryShape, &[ ("query_sql", fingerprint.getattr("definition")?), ("dialect", dialect.clone()), @@ -83,7 +84,7 @@ pub(super) fn candidates<'py>( }; let current = native( py, - "parse_query_shape", + NativeAttr::ParseQueryShape, &[ ("query_sql", model.getattr("query_sql")?), ("dialect", dialect.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_entries.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_entries.rs index 2a358d1938..9f5685f3ca 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_entries.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_entries.rs @@ -1,19 +1,21 @@ //! Materialize column decisions from native proofs and live evidence. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_migrations::column_candidates::declared_names; use crate::bindings::_helpers::planner_migrations::column_objects::{ lower, model, native, replace, }; -use crate::bindings::_helpers::planner_migrations::constants::{AUTOMATIC, DONE, MIGRATION_TYPES}; +use crate::bindings::_helpers::planner_migrations::constants::{AUTOMATIC, DONE}; use crate::bindings::_helpers::planner_migrations::models::{CheckedColumns, ColumnCandidate}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::state_rows::migration_projections::newest_column; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::types::{PyDict, PyDictMethods, PyFrozenSet, PyListMethods, PyTuple}; use sqlbuild_analysis::planner_migrations::main::column_decision::column_decision; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - pub(super) fn decide<'py>( runtime: &Bound<'py, PyAny>, candidate: &ColumnCandidate<'py>, @@ -30,7 +32,7 @@ pub(super) fn decide<'py>( if !candidate.previous.is_none() && !candidate.current.is_none() { let detected = native( py, - "identical_renames", + NativeAttr::IdenticalRenames, &[ ("previous", candidate.previous.clone()), ("current", candidate.current.clone()), @@ -57,12 +59,8 @@ pub(super) fn decide<'py>( } } } - let relation = imported( - py, - "sqlbuild.executor.migrations._helpers.relation_for_location", - "migration_relation_for_location", - )? - .call1((candidate.model.getattr("destination")?,))?; + let relation = host::entry(py, ExecutorHost::MigrationRelationForLocation)? + .call1((candidate.model.getattr("destination")?,))?; let mut entries: Vec> = Vec::new(); for (origin_name, destination, discovery) in pairs { let origin_key = lower(&origin_name)?; @@ -91,14 +89,14 @@ pub(super) fn decide<'py>( outputs.contains(&origin_key), ); let decision = - imported(py, MIGRATION_TYPES, "ColumnMigrationDecision")?.call1((decision_name,))?; + registry::class(py, RecordId::ColumnMigrationDecision)?.call1((decision_name,))?; if discovery == AUTOMATIC && decision.getattr("blocks_build")?.is_truthy()? { continue; } let done = decision_name == DONE && !newest.is_none(); entries.push(model( py, - "ColumnMigrationPlanEntry", + RecordId::ColumnMigrationPlanEntry, &[ ("model_name", candidate.model.getattr("name")?), ("destination", candidate.model.getattr("destination")?), @@ -113,8 +111,7 @@ pub(super) fn decide<'py>( ("destination_column", destination), ( "discovery", - imported(py, NATIVE_RECORDS_MODULE, "MigrationDiscovery")? - .call1((discovery,))?, + registry::class(py, RecordId::MigrationDiscovery)?.call1((discovery,))?, ), ("decision", decision), ( @@ -193,7 +190,7 @@ pub(super) fn checked<'py>( &[ ( "decision", - imported(py, MIGRATION_TYPES, "ColumnMigrationDecision")? + registry::class(py, RecordId::ColumnMigrationDecision)? .getattr("UNSUPPORTED")?, ), ("message", reason.clone()), @@ -228,7 +225,7 @@ pub(super) fn hints<'py>( } native( py, - "rename_hints", + NativeAttr::RenameHints, &[ ("model_name", candidate.model.getattr("name")?), ("previous", candidate.previous.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_objects.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_objects.rs index b70bde8ce7..8408fc47cf 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_objects.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_objects.rs @@ -1,7 +1,8 @@ //! Boundary conversions for the column migration planner. use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; -use crate::bindings::_helpers::planner_migrations::constants::PLANNER_MODELS; +use crate::bindings::_helpers::records::ids::{NativeAttr, NativeItem, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyString}; @@ -26,24 +27,24 @@ pub(super) fn replace<'py>( pub(super) fn model<'py>( py: Python<'py>, - name: &str, + name: RecordId, fields: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - call_with(&imported(py, PLANNER_MODELS, name)?, fields) + call_with(®istry::class(py, name)?, fields) } pub(super) fn native<'py>( py: Python<'py>, - name: &str, + name: impl Into, fields: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - call_with(&imported(py, "sqlbuild._native", name)?, fields) + call_with(®istry::item(py, name)?, fields) } pub(super) fn config<'py>(model: &Bound<'py, PyAny>, key: &str) -> PyResult> { native( model.py(), - "get_config_str", + NativeAttr::GetConfigStr, &[ ("values", model.getattr("config")?.getattr("values")?), ("key", text(model.py(), key)), @@ -57,7 +58,7 @@ pub(super) fn definition_hash<'py>( ) -> PyResult> { native( model.py(), - "model_definition_hash", + NativeAttr::ModelDefinitionHash, &[ ("model_name", model.getattr("name")?), ("query_sql", model.getattr("query_sql")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_overlay.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_overlay.rs index 2fb09ff0e0..0204ab771f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_overlay.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_overlay.rs @@ -7,6 +7,8 @@ use crate::bindings::_helpers::planner_migrations::column_objects::{ config, definition_hash, lower, native, replace, }; use crate::bindings::_helpers::planner_migrations::models::ColumnCandidate; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; @@ -66,7 +68,7 @@ pub(super) fn overlay<'py>( && !candidate.current.is_none() && native( py, - "renames_explain_change", + NativeAttr::RenamesExplainChange, &[ ("previous", candidate.previous.clone()), ("current", candidate.current.clone()), @@ -99,7 +101,7 @@ pub(super) fn overlay<'py>( "fingerprints", native( py, - "WarehouseFingerprints", + RecordId::WarehouseFingerprints, &[ ("models", fingerprints), ("functions", stored.getattr("functions")?), @@ -160,11 +162,7 @@ pub(super) fn cursors<'py>( targets.set_item(name, target)?; } let redirected = call_with( - &imported( - py, - "sqlbuild._native", - "migration_redirected_cursor_snapshots", - )?, + ®istry::native(py, NativeAttr::MigrationRedirectedCursorSnapshots)?, &[ ("runtime", args.value("runtime")?), ("scope", scope), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_planning.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_planning.rs index d3e566786d..902ac4cfec 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_planning.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_planning.rs @@ -1,11 +1,11 @@ //! Host column migration planning while Python owns warehouse inspection. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::planner_migrations::column_objects::native; use crate::bindings::_helpers::planner_migrations::column_objects::replace; use crate::bindings::_helpers::planner_migrations::known_input_columns::KnownInputColumns; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PySet, PySetMethods, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; @@ -43,14 +43,18 @@ fn plan_column_migrations<'py>( &adapter.call_method0("sql_analysis_dialect")?, )?; if candidates.is_empty() { - return native(py, "ColumnMigrationPlanning", &[("snapshot", snapshot)]); + return native( + py, + RecordId::ColumnMigrationPlanning, + &[("snapshot", snapshot)], + ); } let database = call_with( - &imported(py, "sqlbuild._native", "migration_planning_database")?, + ®istry::native(py, NativeAttr::MigrationPlanningDatabase)?, &[("runtime", runtime.clone())], )?; let state = call_with( - &imported(py, "sqlbuild._native", "MigrationStateInspection")?, + ®istry::native(py, NativeAttr::MigrationStateInspection)?, &[ ("adapter", adapter), ("connection", runtime.getattr("connection")?), @@ -97,7 +101,7 @@ fn plan_column_migrations<'py>( warnings.extend(checked.warnings); } let policy = call_with( - &imported(py, "sqlbuild._native", "migration_missing_origin_policy")?, + ®istry::native(py, NativeAttr::MigrationMissingOriginPolicy)?, &[("runtime", runtime)], )?; for entry in &mut entries { @@ -110,7 +114,7 @@ fn plan_column_migrations<'py>( let snapshot = super::column_overlay::cursors(&args, overlaid, &entries)?; native( py, - "ColumnMigrationPlanning", + RecordId::ColumnMigrationPlanning, &[ ("snapshot", snapshot), ("entries", PyTuple::new(py, entries)?.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_warnings.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_warnings.rs index 352ed81e8a..15f6de2d0d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_warnings.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/column_warnings.rs @@ -1,12 +1,13 @@ //! Preserve column-migration diagnostic codes and public messages. -use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::planner_migrations::column_objects::native; use crate::bindings::_helpers::planner_migrations::column_objects::text; use crate::bindings::_helpers::planner_migrations::constants::{ DONE, MISSING_ORIGIN_ALLOW_OUTCOME, MISSING_ORIGIN_CONFIRMATION_OUTCOME, MISSING_ORIGIN_DENY_OUTCOME, }; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::exceptions::PyKeyError; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use sqlbuild_analysis::planner_migrations::main::column_warning::column_warning; @@ -21,12 +22,12 @@ pub(super) fn warning<'py>( let py = entry.py(); native( py, - "PlanWarning", + RecordId::PlanWarning, &[ ("model_name", entry.getattr("model_name")?), ( "severity", - imported(py, "sqlbuild._native", "WarningSeverity")?.getattr(if error { + registry::class(py, RecordId::WarningSeverity)?.getattr(if error { "ERROR" } else { "WARNING" diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/columns.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/columns.rs index a3c0fe63e0..fe781f4777 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/columns.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/columns.rs @@ -11,9 +11,9 @@ use sqlbuild_analysis::planner_migrations::main::{ }; use sqlbuild_analysis::planner_migrations::models::Projection; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; fn projections(shape: &Bound<'_, PyAny>) -> PyResult> { let mut result: Vec = Vec::new(); @@ -119,7 +119,7 @@ fn rename_hints<'py>( let mut result: Vec> = Vec::new(); for hint in hints { result.push(call_with( - &imported(py, "sqlbuild._native", "ColumnRenameHint")?, + ®istry::class(py, RecordId::ColumnRenameHint)?, &[ ("model_name", arguments.value("model_name")?), ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/compatibility.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/compatibility.rs index 85904dedb9..86b6de5fd3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/compatibility.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/compatibility.rs @@ -1,8 +1,8 @@ //! Convert existing planner contracts for origin compatibility checks. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::exceptions::PyValueError; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyTuple, PyTupleMethods}; @@ -10,8 +10,6 @@ use pyo3::{IntoPyObject, pyfunction, wrap_pyfunction}; use sqlbuild_analysis::planner_migrations::main::compatibility_findings::compatibility_findings; use sqlbuild_analysis::planner_migrations::models::CompatibilityFinding; -const TYPES_MODULE: &str = "sqlbuild._native"; - fn config(model: &Bound<'_, PyAny>, key: &str) -> PyResult> { model .getattr("config")? @@ -40,13 +38,12 @@ fn blocking<'py>( .filter(|value| !value.is_empty()) .unwrap_or_else(|| "append_new_columns".to_owned()) }; - imported( + registry::class( py, - TYPES_MODULE, if snapshot { - "SnapshotSchemaChangePolicy" + RecordId::SnapshotSchemaChangePolicy } else { - "OnSchemaChange" + RecordId::OnSchemaChange }, )? .call1((&policy,))?; @@ -142,7 +139,7 @@ fn check_migration_compatibility<'py>( let columns = PyDict::new(py); columns.set_item(model.getattr("name")?, arguments.value("origin_columns")?)?; let snapshot = call_with( - &imported(py, "sqlbuild._native", "WarehouseSnapshot")?, + ®istry::class(py, RecordId::WarehouseSnapshot)?, &[ ("existing_relations", relations.into_any()), ("existing_columns", columns.into_any()), @@ -150,7 +147,7 @@ fn check_migration_compatibility<'py>( ], )?; let change = call_with( - &imported(py, "sqlbuild._native", "detect_model_changes")?, + ®istry::native(py, NativeAttr::DetectModelChanges)?, &[ ("model", model.clone()), ("snapshot", snapshot), @@ -205,9 +202,9 @@ fn result<'py>( status: &str, findings: Bound<'py, PyTuple>, ) -> PyResult> { - let status = imported(py, "sqlbuild._native", "MigrationCompatibility")?.call1((status,))?; + let status = registry::class(py, RecordId::MigrationCompatibility)?.call1((status,))?; call_with( - &imported(py, "sqlbuild._native", "MigrationCompatibilityResult")?, + ®istry::class(py, RecordId::MigrationCompatibilityResult)?, &[("status", status), ("findings", findings.into_any())], ) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/constants.rs index 5a37210972..7dbe08a7b3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/constants.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/constants.rs @@ -1,18 +1,11 @@ -pub(super) const PLANNER_MODELS: &str = "sqlbuild._native"; -pub(super) const MIGRATION_TYPES: &str = "sqlbuild._native"; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; pub(super) const AUTOMATIC: &str = "automatic"; pub(super) const DONE: &str = "done"; pub(super) const PENDING_ARCHIVE: &str = "pending_archive"; pub(super) const PENDING_VIEW: &str = "pending_view"; -pub(super) const NATIVE_MODULE: &str = "sqlbuild._native"; -pub(super) const LIST_RELATIONS_FOR_INSPECTION: (&str, &str) = ( - "sqlbuild.adapter.relations.main._list_relations_for_inspection", - "list_relations_for_inspection", -); -pub(super) const GET_COLUMNS_FOR_INSPECTION: (&str, &str) = ( - "sqlbuild.adapter.relations.main._get_columns_for_inspection", - "get_columns_for_inspection", -); +pub(super) const LIST_RELATIONS_FOR_INSPECTION: AdapterHost = + AdapterHost::ListRelationsForInspection; +pub(super) const GET_COLUMNS_FOR_INSPECTION: AdapterHost = AdapterHost::GetColumnsForInspection; pub(super) const QUALIFIED_RELATION_MAX_PARTS: usize = 3; pub(super) const MIGRATION_MODEL_NAME_METADATA_KEY: &str = "model_name"; pub(super) const HIDDEN_ORIGIN_REMEDY: &str = "the connection may lack permission to see it, or it was dropped outside SQLBuild; restore access to it, or remove migrate_from to build the model fresh deliberately (missing_migration_origin does not apply)"; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery.rs index 7b4aa59682..24535fae12 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery.rs @@ -5,6 +5,7 @@ use crate::bindings::_helpers::planner_migrations::column_objects::native; use crate::bindings::_helpers::planner_migrations::{ discovery_events, discovery_models, discovery_requests, }; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyFrozenSet, PyListMethods, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; @@ -46,7 +47,7 @@ fn discover_model_migrations<'py>( let triggered = discovery_models::triggered(&models, &scope, &args.value("snapshot")?, &declared_names)?; if !triggered && !manual.is_truthy()? { - return native(py, "ModelMigrationDiscovery", &[]); + return native(py, RecordId::ModelMigrationDiscovery, &[]); } let metadata = discovery_models::metadata(&runtime, &models)?; let dialect = runtime @@ -70,7 +71,7 @@ fn discover_model_migrations<'py>( if !triggered { let hashes = native( py, - "migration_destination_fingerprints", + NativeAttr::MigrationDestinationFingerprints, &[ ("requests", manual.clone()), ("variants", seeds.into_any()), @@ -81,7 +82,7 @@ fn discover_model_migrations<'py>( )?; return native( py, - "ModelMigrationDiscovery", + RecordId::ModelMigrationDiscovery, &[("requests", manual), ("destination_fingerprints", hashes)], ); } @@ -106,7 +107,7 @@ fn discover_model_migrations<'py>( } let matched = native( py, - "match_migration_fingerprints", + NativeAttr::MatchMigrationFingerprints, &[ ("models", PyTuple::new(py, &models)?.into_any()), ( @@ -131,7 +132,7 @@ fn discover_model_migrations<'py>( let warnings = discovery_requests::warnings(&models, &scope, &matched.get_item(2)?)?; let hashes = native( py, - "migration_destination_fingerprints", + NativeAttr::MigrationDestinationFingerprints, &[ ("requests", requests.clone().into_any()), ("variants", matched.get_item(0)?), @@ -142,7 +143,7 @@ fn discover_model_migrations<'py>( )?; native( py, - "ModelMigrationDiscovery", + RecordId::ModelMigrationDiscovery, &[ ("requests", requests.into_any()), ("warnings", warnings.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery_events.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery_events.rs index b4e58f3c62..07254edf57 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery_events.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery_events.rs @@ -1,6 +1,8 @@ //! Preserve event and declared-origin precedence at the inspection boundary. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_migrations::column_objects::config; use crate::bindings::_helpers::state_rows::migration_projections::newest_move; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; @@ -20,22 +22,13 @@ pub(super) fn newest<'py>( events: &Bound<'py, PyAny>, location: &Bound<'py, PyAny>, ) -> PyResult> { - let relation = imported( - location.py(), - "sqlbuild.executor.migrations._helpers.relation_for_location", - "migration_relation_for_location", - )? - .call1((location,))?; + let relation = host::entry(location.py(), ExecutorHost::MigrationRelationForLocation)? + .call1((location,))?; newest_move(events, &relation) } pub(super) fn relation<'py>(location: &Bound<'py, PyAny>) -> PyResult> { - imported( - location.py(), - "sqlbuild.executor.migrations._helpers.relation_for_location", - "migration_relation_for_location", - )? - .call1((location,)) + host::entry(location.py(), ExecutorHost::MigrationRelationForLocation)?.call1((location,)) } pub(super) fn declared<'py>( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery_models.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery_models.rs index 3542ad0358..6676aacc5f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery_models.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery_models.rs @@ -1,10 +1,16 @@ //! Model ordering, selection and metadata at the migration discovery boundary. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::graph::node_set::PyNodeSet; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; use crate::bindings::_helpers::planner_migrations::column_objects::native; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::types::{PyDict, PyDictMethods, PySet, PySetMethods}; use sqlbuild_analysis::planner_migrations::main::discovery_order::discovery_order; +use sqlbuild_core::graph_walk::main::breadth_first_reach::breadth_first_reach; use std::collections::BTreeMap; pub(super) fn ordered<'py>(runtime: &Bound<'py, PyAny>) -> PyResult>> { @@ -40,20 +46,15 @@ pub(super) fn triggered( .getattr("selected_keys")? .try_iter()? .collect::>()?; - let relevant = PySet::new(py, &selected)?; - let mut frontier = selected; - while let Some(key) = frontier.pop() { - let upstream = scope + let mut relevant = PyNodeSet(PySet::new(py, &selected)?); + breadth_first_reach(selected, None, &mut relevant, |key| { + scope .getattr("upstream_deps")? - .call_method1("get", (key, pyo3::types::PyTuple::empty(py)))?; - for dependency in upstream.try_iter()? { - let dependency = dependency?; - if !relevant.contains(&dependency)? { - relevant.add(&dependency)?; - frontier.push(dependency); - } - } - } + .call_method1("get", (key, pyo3::types::PyTuple::empty(py)))? + .try_iter()? + .collect::>>>() + })?; + let relevant = relevant.0; let fingerprints = snapshot.getattr("fingerprints")?.getattr("models")?; for item in models { let name = item.getattr("name")?; @@ -75,18 +76,14 @@ pub(super) fn metadata<'py>( let project = runtime.getattr("project")?; let functions = native( py, - "build_function_local_hashes", + NativeAttr::BuildFunctionLocalHashes, &[ ("functions", project.getattr("functions")?), ("dialect", project.getattr("sql_analysis_dialect")?), ], )?; - let identities = imported( - py, - "sqlbuild.compiler.python_nodes.main._hook_identities", - "build_hook_identities", - )? - .call1((project.getattr("hook_functions")?,))?; + let identities = host::entry(py, UserCodeHost::BuildHookIdentities)? + .call1((project.getattr("hook_functions")?,))?; let hooks = PyDict::new(py); for pair in identities.call_method0("items")?.try_iter()? { let pair = pair?; @@ -100,11 +97,7 @@ pub(super) fn metadata<'py>( result.set_item( item.getattr("name")?, call_with( - &imported( - py, - "sqlbuild._native", - "build_model_version_identity_metadata_json", - )?, + ®istry::native(py, NativeAttr::BuildModelVersionIdentityMetadataJson)?, &[ ("model", item.clone()), ("function_local_hashes", functions.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery_requests.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery_requests.rs index 5ef0c9fcb1..205e96846b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery_requests.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery_requests.rs @@ -1,11 +1,13 @@ //! Live candidate inspection and automatic request materialization. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::planner_migrations::column_objects::config; use crate::bindings::_helpers::planner_migrations::column_objects::native; use crate::bindings::_helpers::planner_migrations::discovery_events::{ identity, key, newest, relation, }; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::types::{ PyBool, PyDict, PyDictMethods, PyFrozenSet, PyListMethods, PySet, PySetMethods, PyString, @@ -140,13 +142,12 @@ pub(super) fn automatic<'py>( if let Some((location, origin_model)) = origin { requests.push(native( py, - "ModelMigrationRequest", + RecordId::ModelMigrationRequest, &[ ("model", item.clone()), ( "discovery", - imported(py, "sqlbuild._native", "MigrationDiscovery")? - .getattr("AUTOMATIC")?, + registry::class(py, RecordId::MigrationDiscovery)?.getattr("AUTOMATIC")?, ), ("origin_location", location), ("origin_model", origin_model), @@ -180,9 +181,9 @@ pub(super) fn warnings<'py>( let mut warnings: Vec> = Vec::new(); for (name, detail) in ambiguous { if selected.contains(&name)? { - warnings.push(native(py, "PlanWarning", &[ + warnings.push(native(py, RecordId::PlanWarning, &[ ("model_name", PyString::new(py, &name).into_any()), - ("severity", imported(py, "sqlbuild._native", "WarningSeverity")?.getattr("WARNING")?), + ("severity", registry::class(py, RecordId::WarningSeverity)?.getattr("WARNING")?), ("message", PyString::new(py, &format!("'{name}' matches {detail}; no automatic migration was inferred. Add migrate_from to choose one.")).into_any()), ("code", PyString::new(py, "M107").into_any()), ])?); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/model_entries.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/model_entries.rs index 86203c56d0..59a4408c60 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/model_entries.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/model_entries.rs @@ -1,29 +1,23 @@ //! Assemble model migration decisions from inspected warehouse facts. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_migrations::column_objects::{ lower, model, native, replace, text, }; -use crate::bindings::_helpers::planner_migrations::constants::MIGRATION_TYPES; use crate::bindings::_helpers::planner_migrations::model_handover::{ applies, empty_version, handover, }; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::state_rows::migration_projections::newest_move; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::types::{PyBool, PyTuple}; use sqlbuild_analysis::planner_migrations::main::model_decision::model_decision; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - pub(super) fn relation<'py>(location: &Bound<'py, PyAny>) -> PyResult> { - imported( - location.py(), - "sqlbuild.executor.migrations._helpers.relation_for_location", - "migration_relation_for_location", - )? - .call1((location,)) + host::entry(location.py(), ExecutorHost::MigrationRelationForLocation)?.call1((location,)) } fn key(location: &Bound<'_, PyAny>) -> PyResult<(String, String)> { @@ -43,7 +37,7 @@ pub(super) fn decision<'py>(entry: &Bound<'py, PyAny>, name: &str) -> PyResult(args: &KeywordArguments<'py>) -> PyResult(args: &KeywordArguments<'py>) -> PyResult(args: &KeywordArguments<'py>) -> PyResult(args: &KeywordArguments<'py>) -> PyResult( let py = model.py(); if handover.is_none() || !destinations.contains( - imported(py, "sqlbuild._native", "stored_migration_fingerprint")? - .call1((&handover,))?, + registry::native(py, NativeAttr::StoredMigrationFingerprint)?.call1((&handover,))?, )? { return Ok(handover); @@ -93,7 +94,7 @@ pub(super) fn equivalent<'py>( "definition_hash", native( py, - "compute_query_hash", + NativeAttr::ComputeQueryHash, &[ ("query_sql", model.getattr("query_sql")?), ("dialect", dialect.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/model_overlay.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/model_overlay.rs index 56eab7c306..a224f6ec86 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/model_overlay.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/model_overlay.rs @@ -1,10 +1,10 @@ //! Project inherited fingerprints and live relation facts before normal planning. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::planner_migrations::column_objects::native; use crate::bindings::_helpers::planner_migrations::column_objects::{config, replace}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::types::{PyDict, PyDictMethods}; @@ -88,11 +88,7 @@ pub(super) fn overlay<'py>( .cast_into::()? .copy()?; let gathered = call_with( - &imported( - py, - "sqlbuild._native", - "migration_redirected_cursor_snapshots", - )?, + ®istry::native(py, NativeAttr::MigrationRedirectedCursorSnapshots)?, &[ ("runtime", runtime), ("scope", args.value("scope")?), @@ -112,7 +108,7 @@ pub(super) fn overlay<'py>( "fingerprints", native( py, - "WarehouseFingerprints", + RecordId::WarehouseFingerprints, &[ ("models", models.into_any()), ("functions", fingerprints.getattr("functions")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/model_planning.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/model_planning.rs index 99344dbef2..687f6d4239 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/model_planning.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/model_planning.rs @@ -1,17 +1,15 @@ //! Model migration orchestration over Python-owned live inspection. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::planner_migrations::column_objects::{config, native, replace}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{ PyBool, PyDict, PyDictMethods, PyFrozenSet, PySet, PySetMethods, PyString, PyTuple, }; use pyo3::{pyfunction, wrap_pyfunction}; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - fn declared<'py>(item: &Bound<'py, PyAny>) -> PyResult>> { let py = item.py(); let values = item.getattr("config")?.getattr("values")?; @@ -22,12 +20,12 @@ fn declared<'py>(item: &Bound<'py, PyAny>) -> PyResult> let materialization = config(item, "materialized")?; Ok(Some(native( py, - "ModelMigrationRequest", + RecordId::ModelMigrationRequest, &[ ("model", item.clone()), ( "discovery", - imported(py, NATIVE_RECORDS_MODULE, "MigrationDiscovery")?.getattr("MANUAL")?, + registry::class(py, RecordId::MigrationDiscovery)?.getattr("MANUAL")?, ), ("raw_origin", origin.call_method0("strip")?), ( @@ -127,18 +125,18 @@ fn plan_model_migrations<'py>( let runtime = args.value("runtime")?; let snapshot = args.value("snapshot")?; let schemas = call_with( - &imported(py, "sqlbuild._native", "migration_project_schemas")?, + ®istry::native(py, NativeAttr::MigrationProjectSchemas)?, &[("runtime", runtime.clone())], )?; let state = call_with( - &imported(py, "sqlbuild._native", "MigrationStateInspection")?, + ®istry::native(py, NativeAttr::MigrationStateInspection)?, &[ ("adapter", runtime.getattr("adapter")?), ("connection", runtime.getattr("connection")?), ( "database", call_with( - &imported(py, "sqlbuild._native", "migration_planning_database")?, + ®istry::native(py, NativeAttr::MigrationPlanningDatabase)?, &[("runtime", runtime.clone())], )?, ), @@ -173,7 +171,7 @@ fn discover<'py>( let mut declarations: Vec> = Vec::new(); for request in declared_requests { let resolved = call_with( - &imported(py, "sqlbuild._native", "migration_resolve_origin")?, + ®istry::native(py, NativeAttr::MigrationResolveOrigin)?, &[ ("request", request.clone()), ("runtime", runtime.clone()), @@ -182,7 +180,7 @@ fn discover<'py>( )?; declarations.push(native( py, - "ModelMigrationDeclaration", + RecordId::ModelMigrationDeclaration, &[ ("model_name", request.getattr("model")?.getattr("name")?), ("origin_location", resolved.get_item(0)?), @@ -191,7 +189,7 @@ fn discover<'py>( )?); } call_with( - &imported(py, "sqlbuild._native", "discover_model_migrations")?, + ®istry::native(py, NativeAttr::DiscoverModelMigrations)?, &[ ("runtime", runtime.clone()), ("scope", scope.clone()), @@ -219,7 +217,7 @@ fn finish<'py>( for request in discovery.getattr("requests")?.try_iter()? { let request = request?; let origin = call_with( - &imported(py, "sqlbuild._native", "migration_resolve_origin")?, + ®istry::native(py, NativeAttr::MigrationResolveOrigin)?, &[ ("request", request.clone()), ("runtime", runtime.clone()), @@ -235,7 +233,7 @@ fn finish<'py>( let mut origins: Vec> = Vec::new(); for (_, origin, _) in &resolved { call_with( - &imported(py, "sqlbuild._native", "migration_require_same_database")?, + ®istry::native(py, NativeAttr::MigrationRequireSameDatabase)?, &[("state", state.clone()), ("location", origin.clone())], )?; let schema = origin.getattr("schema")?; @@ -254,12 +252,12 @@ fn finish<'py>( )?; let project_config = runtime.getattr("project_config")?; let snapshots_config = if project_config.is_none() { - imported(py, "sqlbuild._native", "SnapshotsConfig")?.call0()? + registry::class(py, RecordId::SnapshotsConfig)?.call0()? } else { project_config.getattr("snapshots")? }; let policy = call_with( - &imported(py, "sqlbuild._native", "migration_missing_origin_policy")?, + ®istry::native(py, NativeAttr::MigrationMissingOriginPolicy)?, &[("runtime", runtime.clone())], )?; for (request, origin, origin_model) in resolved { @@ -267,7 +265,7 @@ fn finish<'py>( let name = item.getattr("name")?; let decided = native( py, - "decide_model_migration", + NativeAttr::DecideModelMigration, &[ ("runtime", runtime.clone()), ("request", request), @@ -326,7 +324,7 @@ fn finish<'py>( }; let reference_renames = native( py, - "plan_reference_renames", + NativeAttr::PlanReferenceRenames, &[ ("runtime", runtime), ("scope", scope), @@ -342,7 +340,7 @@ fn finish<'py>( } native( py, - "ModelMigrationPlanning", + RecordId::ModelMigrationPlanning, &[ ("snapshot", replace(&overlaid, &fields)?), ("entries", PyTuple::new(py, entries)?.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_name_entries.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_name_entries.rs index 42506b327f..8265c05d0f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_name_entries.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_name_entries.rs @@ -9,6 +9,8 @@ use crate::bindings::_helpers::planner_migrations::models::OldNamePlanner; use crate::bindings::_helpers::planner_migrations::old_name_objects::{ expires, location, relation, retention, stores_history, }; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::types::{PyBool, PyList, PyTuple}; @@ -23,7 +25,7 @@ impl<'py> OldNamePlanner<'py> { let py = runtime.py(); let project_config = runtime.getattr("project_config")?; let config = if project_config.is_none() { - imported(py, "sqlbuild._native", "MigrationsConfig")?.call0()? + registry::class(py, RecordId::MigrationsConfig)?.call0()? } else { project_config.getattr("migrations")? }; @@ -137,7 +139,7 @@ impl<'py> OldNamePlanner<'py> { ) -> PyResult> { native( self.runtime.py(), - "old_name_view_skip_reason", + NativeAttr::OldNameViewSkipReason, &[ ("origin", origin.clone()), ("origin_tracked", tracked), @@ -154,7 +156,7 @@ impl<'py> OldNamePlanner<'py> { fn aliases(&self, item: &Bound<'py, PyAny>) -> PyResult> { let py = self.runtime.py(); if !stores_history(item)? { - return imported(py, "sqlbuild._native", "declared_column_aliases")?.call1((item,)); + return registry::native(py, NativeAttr::DeclaredColumnAliases)?.call1((item,)); } let mut aliases: Vec> = Vec::new(); for entry in self.columns.try_iter()? { @@ -229,7 +231,7 @@ impl<'py> OldNamePlanner<'py> { ), ]); } - model(py, "OldNameViewPlanEntry", &fields) + model(py, RecordId::OldNameViewPlanEntry, &fields) } fn resume( @@ -255,7 +257,7 @@ impl<'py> OldNamePlanner<'py> { if !reason.is_none() { return model( py, - "OldNameViewPlanEntry", + RecordId::OldNameViewPlanEntry, &[ ( "model_name", @@ -282,7 +284,7 @@ impl<'py> OldNamePlanner<'py> { } } let retention = history.getattr("required")?.getattr("view_retention")?; - let duration = imported(py, "sqlbuild._native", "Duration")?.call_method1( + let duration = registry::class(py, RecordId::Duration)?.call_method1( "parse", (if retention.is_truthy()? { retention.clone() @@ -305,7 +307,7 @@ impl<'py> OldNamePlanner<'py> { }; model( py, - "OldNameViewPlanEntry", + RecordId::OldNameViewPlanEntry, &[ ("model_name", item.getattr("name")?), ("origin", location(&self.runtime, &history.getattr("old")?)?), @@ -363,5 +365,5 @@ impl<'py> OldNamePlanner<'py> { } fn action<'py>(runtime: &Bound<'py, PyAny>, name: &str) -> PyResult> { - imported(runtime.py(), "sqlbuild._native", "OldNameViewAction")?.getattr(name) + registry::class(runtime.py(), RecordId::OldNameViewAction)?.getattr(name) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_name_objects.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_name_objects.rs index 140befe160..282804b077 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_name_objects.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_name_objects.rs @@ -1,17 +1,16 @@ //! Old-name planner object conversions and existing native helper calls. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_migrations::column_objects::{config, model}; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::state_rows::migration_projections::old_name_retention; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; pub(super) fn relation<'py>(location: &Bound<'py, PyAny>) -> PyResult> { - imported( - location.py(), - "sqlbuild.executor.migrations._helpers.relation_for_location", - "migration_relation_for_location", - )? - .call1((location,)) + host::entry(location.py(), ExecutorHost::MigrationRelationForLocation)?.call1((location,)) } pub(super) fn location<'py>( @@ -31,7 +30,7 @@ pub(super) fn location<'py>( )?; fields.push(("qualified_name", qualified)); call_with( - &imported(runtime.py(), "sqlbuild._native", "CompiledRelationLocation")?, + ®istry::class(runtime.py(), RecordId::CompiledRelationLocation)?, &fields, ) } @@ -53,7 +52,7 @@ pub(super) fn expires<'py>( now: &Bound<'py, PyAny>, ) -> PyResult> { let duration = - imported(now.py(), "sqlbuild._native", "Duration")?.call_method1("parse", (retention,))?; + registry::class(now.py(), RecordId::Duration)?.call_method1("parse", (retention,))?; if duration.is_none() { Ok(now.py().None().into_bound(now.py())) } else { @@ -81,7 +80,7 @@ pub(super) fn recorded_view<'py>( } model( py, - "OldNameView", + RecordId::OldNameView, &[ ("destination_model", movement.getattr("destination_model")?), ("old", location(runtime, &history.getattr("old")?)?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_name_planning.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_name_planning.rs index eb1ea8fe69..3fe90cdd55 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_name_planning.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_name_planning.rs @@ -1,10 +1,12 @@ //! Old-name planning orchestration over existing live state inspection. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_migrations::column_objects::native; use crate::bindings::_helpers::planner_migrations::old_name_objects::recorded_view; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PySet, PySetMethods, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; @@ -39,13 +41,9 @@ fn schemas<'py>( .getattr("existing_relations")? .call_method1("get", (item.getattr("name")?,))?; if !existing.is_none() - && imported( - py, - "sqlbuild.adapter.type_system.main.normalize_relation_type", - "normalize_relation_type", - )? - .call1((existing.getattr("relation_type")?,))? - .eq("view")? + && host::entry(py, AdapterHost::NormalizeRelationType)? + .call1((existing.getattr("relation_type")?,))? + .eq("view")? { include_project = true; break; @@ -53,14 +51,18 @@ fn schemas<'py>( } } if include_project { - let project = native(py, "migration_project_schemas", &[("runtime", runtime)])?; + let project = native( + py, + NativeAttr::MigrationProjectSchemas, + &[("runtime", runtime)], + )?; for schema in project.try_iter()? { needed.add(schema?)?; } } native( py, - "old_name_schemas_to_read", + NativeAttr::OldNameSchemasToRead, &[("snapshot", snapshot), ("needed", needed.into_any())], ) } @@ -108,7 +110,7 @@ fn plan_old_name_views<'py>( } } let state = call_with( - &imported(py, "sqlbuild._native", "MigrationStateInspection")?, + ®istry::native(py, NativeAttr::MigrationStateInspection)?, &[ ("adapter", runtime.getattr("adapter")?), ("connection", runtime.getattr("connection")?), @@ -116,7 +118,7 @@ fn plan_old_name_views<'py>( "database", native( py, - "migration_planning_database", + NativeAttr::MigrationPlanningDatabase, &[("runtime", runtime.clone())], )?, ), @@ -167,7 +169,7 @@ fn plan_old_name_views<'py>( let entries = PyTuple::new(py, entries)?.into_any(); let claims = native( py, - "old_name_claim_errors", + NativeAttr::OldNameClaimErrors, &[ ("selected", PyTuple::new(py, selected)?.into_any()), ("snapshot", args.value("snapshot")?), @@ -181,7 +183,7 @@ fn plan_old_name_views<'py>( )?); native( py, - "OldNameViewPlanning", + RecordId::OldNameViewPlanning, &[ ("entries", entries), ("views", views), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_name_references.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_name_references.rs index ee9fe726d2..994f90033c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_name_references.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_name_references.rs @@ -1,7 +1,11 @@ //! Diagnose literal references to compatibility-only old names. use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; use crate::bindings::_helpers::planner_migrations::column_objects::{native, text}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::types::{PyDict, PyDictMethods, PyStringMethods, PyTuple}; @@ -11,12 +15,11 @@ fn old_relation<'py>( ) -> PyResult> { let py = old.py(); let reference = call_with( - &imported(py, "sqlbuild.python_nodes.models", "SqlResourceRef")?, + &host::entry(py, UserCodeHost::SqlResourceRef)?, &[ ( "kind", - imported(py, "sqlbuild.python_nodes.types", "SqlResourceRefKind")? - .getattr("MODEL")?, + host::entry(py, UserCodeHost::SqlResourceRefKind)?.getattr("MODEL")?, ), ("name", name.clone()), ], @@ -82,7 +85,7 @@ pub(super) fn errors<'py>( let relation = literal.getattr("relation")?; let matched = native( py, - "match_project_relation", + NativeAttr::MatchProjectRelation, &[("index", index.clone()), ("relation", relation.clone())], )?; if matched.is_none() || matched.getattr("compatibility_for")?.is_none() { @@ -97,7 +100,7 @@ pub(super) fn errors<'py>( } let message: String = native( py, - "old_name_reference_message", + NativeAttr::OldNameReferenceMessage, &[ ("owner_label", literal.getattr("owner_label")?), ("written", text(py, &parts.join("."))), @@ -108,7 +111,7 @@ pub(super) fn errors<'py>( .extract()?; let remedy: String = native( py, - "hard_coded_relation_remedy", + NativeAttr::HardCodedRelationRemedy, &[ ("owner_kind", literal.getattr("owner_kind")?), ("ref", matched.getattr("ref")?), @@ -123,12 +126,12 @@ pub(super) fn errors<'py>( let line = literal.getattr("line")?.str()?.to_str()?.to_owned(); errors.push(native( py, - "PlanWarning", + RecordId::PlanWarning, &[ ("model_name", py.None().into_bound(py)), ( "severity", - imported(py, "sqlbuild._native", "WarningSeverity")?.getattr("ERROR")?, + registry::class(py, RecordId::WarningSeverity)?.getattr("ERROR")?, ), ( "message", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_names.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_names.rs index 44af340e9b..f8116bae5b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_names.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/old_names.rs @@ -1,9 +1,11 @@ //! Existing old-name planner models at the native decision boundary. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_migrations::old_name_objects::relation; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PySet, PyTuple}; use pyo3::{IntoPyObject, pyfunction, wrap_pyfunction}; @@ -115,12 +117,8 @@ fn old_name_claim_errors<'py>( if existing.is_none() { continue; } - let kind = imported( - py, - "sqlbuild.adapter.type_system.main.normalize_relation_type", - "normalize_relation_type", - )? - .call1((existing.getattr("relation_type")?,))?; + let kind = host::entry(py, AdapterHost::NormalizeRelationType)? + .call1((existing.getattr("relation_type")?,))?; if !kind.eq("view")? { continue; } @@ -137,12 +135,12 @@ fn old_name_claim_errors<'py>( let message = claim_message(&name.extract::()?, &view, &arguments.value("now")?)?; errors.push(call_with( - &imported(py, "sqlbuild._native", "PlanWarning")?, + ®istry::class(py, RecordId::PlanWarning)?, &[ ("model_name", name.clone()), ( "severity", - imported(py, "sqlbuild._native", "WarningSeverity")?.getattr("ERROR")?, + registry::class(py, RecordId::WarningSeverity)?.getattr("ERROR")?, ), ("message", message.into_pyobject(py)?.into_any()), ("code", "M114".into_pyobject(py)?.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/planning_boundary.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/planning_boundary.rs index 0ad044005b..05ae35389a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/planning_boundary.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/planning_boundary.rs @@ -1,9 +1,9 @@ //! Assemble migration policy and redirected live cursor inspection requests. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::planner_migrations::column_objects::native; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::PyDict; use pyo3::{pyfunction, wrap_pyfunction}; @@ -14,7 +14,7 @@ fn migration_missing_origin_policy<'py>( runtime: &Bound<'py, PyAny>, ) -> PyResult> { call_with( - &imported(runtime.py(), "sqlbuild._native", "effective_target_config")?, + ®istry::native(runtime.py(), NativeAttr::EffectiveTargetConfig)?, &[("runtime", runtime.clone())], )? .getattr("missing_migration_origin") @@ -54,7 +54,7 @@ fn migration_redirected_cursor_snapshots<'py>( }; let cursor_scope = native( py, - "CursorSnapshotScope", + RecordId::CursorSnapshotScope, &[ ("model_keys", scope.getattr("selected_keys")?), ("runtime_producer_keys", scope.getattr("selected_keys")?), @@ -64,18 +64,14 @@ fn migration_redirected_cursor_snapshots<'py>( ], )?; let full_refresh = call_with( - &imported( - py, - "sqlbuild._native", - "effectively_full_refreshed_model_names", - )?, + ®istry::native(py, NativeAttr::EffectivelyFullRefreshedModelNames)?, &[ ("project", project.clone()), ("cli_full_refresh", overrides.getattr("full_refresh")?), ], )?; call_with( - &imported(py, "sqlbuild._native", "gather_redirected_cursor_snapshots")?, + ®istry::native(py, NativeAttr::GatherRedirectedCursorSnapshots)?, &[ ("project", project), ("adapter", runtime.getattr("adapter")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/planning_execution.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/planning_execution.rs index f0c0e34069..07f52831e7 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/planning_execution.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/planning_execution.rs @@ -1,9 +1,11 @@ //! Preserve adapter overrides when describing migration execution. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; -use crate::bindings::_helpers::planner_migrations::{ - column_objects::replace, constants::MIGRATION_TYPES, -}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::planner_migrations::column_objects::replace; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult}; use pyo3::types::PyBool; use pyo3::{pyfunction, wrap_pyfunction}; @@ -24,7 +26,7 @@ fn migration_promotion<'py>( decision: &Bound<'py, PyAny>, ) -> PyResult> { let adapter = runtime.getattr("adapter")?; - let promotion = imported(runtime.py(), MIGRATION_TYPES, "MigrationPromotion")?; + let promotion = registry::class(runtime.py(), RecordId::MigrationPromotion)?; if adapter .call_method0("supports_transactional_ddl")? .is_truthy()? @@ -71,7 +73,7 @@ fn migration_with_execution<'py>( )?; let transfer = stage.getattr("transfer")?; let fallback = if stage.getattr("fallback_statements")?.is_truthy()? { - imported(py, "sqlbuild.adapter.contract.types", "MigrationTransfer")?.getattr("COPY")? + host::entry(py, AdapterHost::MigrationTransfer)?.getattr("COPY")? } else { py.None().into_bound(py) }; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/planning_origin.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/planning_origin.rs index 975eaeb167..3a14bdae93 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/planning_origin.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/planning_origin.rs @@ -1,7 +1,9 @@ //! Resolve authored migration origins at the adapter and inspection boundaries. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::planner_migrations::constants::QUALIFIED_RELATION_MAX_PARTS; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PySet, PySetMethods, PyTuple}; use pyo3::{PyErr, pyfunction, wrap_pyfunction}; @@ -19,7 +21,7 @@ pub(super) fn location<'py>( let mut fields = fields.to_vec(); fields.push(("qualified_name", qualified)); call_with( - &imported(runtime.py(), "sqlbuild._native", "CompiledRelationLocation")?, + ®istry::class(runtime.py(), RecordId::CompiledRelationLocation)?, &fields, ) } @@ -61,7 +63,7 @@ fn named<'py>( fn input_error(py: Python<'_>, message: String) -> PyResult { Ok(PyErr::from_value( - imported(py, "sqlbuild._native", "PlannerInputError")?.call1((message,))?, + registry::error(py, ErrorId::PlannerInput)?.call1((message,))?, )) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/projections.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/projections.rs index c16a6abc8f..cf135f6f12 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/projections.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/projections.rs @@ -1,9 +1,9 @@ //! Adapt existing Python query shape models to pure migration proofs. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::planner_migrations::errors::ProofError; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::exceptions::PyValueError; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyFrozenSet, PyTuple}; @@ -54,7 +54,7 @@ fn parse_query_shape<'py>( let mut projections: Vec> = Vec::new(); for item in shape.projections { projections.push(call_with( - &imported(py, "sqlbuild._native", "QueryProjection")?, + ®istry::class(py, RecordId::QueryProjection)?, &[ ("name", item.name.into_pyobject(py)?.into_any()), ("expression", item.expression.into_pyobject(py)?.into_any()), @@ -74,7 +74,7 @@ fn parse_query_shape<'py>( None => py.None().into_bound(py), }; call_with( - &imported(py, "sqlbuild._native", "QueryShape")?, + ®istry::class(py, RecordId::QueryShape)?, &[ ("projections", PyTuple::new(py, projections)?.into_any()), ("body", shape.body.into_pyobject(py)?.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/state_inspection.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/state_inspection.rs index ad53285986..ad6d16ca7e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/state_inspection.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/state_inspection.rs @@ -12,10 +12,13 @@ use sqlbuild_core::constants::OLD_NAME_VIEW_TABLE_NAME; use sqlbuild_state::events::main::newest_fingerprint::newest_fingerprint; use sqlbuild_state::events::models::{EventRelation, FingerprintKey, FingerprintOrder}; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_migrations::constants::{ - GET_COLUMNS_FOR_INSPECTION, LIST_RELATIONS_FOR_INSPECTION, NATIVE_MODULE, + GET_COLUMNS_FOR_INSPECTION, LIST_RELATIONS_FOR_INSPECTION, }; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::state_rows::constants::{COLUMN_KIND, MODEL_KIND, OLD_NAME_KIND}; use crate::bindings::_helpers::state_rows::migration_io::{read_events, stored_old_name_columns}; use crate::bindings::_helpers::state_rows::migration_objects::layout; @@ -145,9 +148,8 @@ impl MigrationStateInspection { schemas: Vec>, names: Bound<'py, PyAny>, ) -> PyResult>> { - let (module, name) = LIST_RELATIONS_FOR_INSPECTION; call_with( - &imported(py, module, name)?, + &host::entry(py, LIST_RELATIONS_FOR_INSPECTION)?, &[ ("adapter", self.adapter.bind(py).clone()), ("connection", self.connection.bind(py).clone()), @@ -173,7 +175,7 @@ impl MigrationStateInspection { let latest: Bound<'py, PyAny> = if known.is_none() { let adapter: &Bound<'py, PyAny> = self.adapter.bind(py); let fingerprint_set: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "read_latest_fingerprints")?, + ®istry::native(py, NativeAttr::ReadLatestFingerprints)?, &[ ("connection", self.connection.bind(py).clone()), ("execute", adapter.getattr("execute")?), @@ -479,13 +481,12 @@ impl MigrationStateInspection { let columns: Bound<'_, PyAny> = if matched.is_empty() { PyDict::new(py).into_any() } else { - let (module, name) = GET_COLUMNS_FOR_INSPECTION; let this: PyRef<'_, Self> = slf.borrow(); let adapter: Bound<'_, PyAny> = this.adapter.bind(py).clone(); let connection: Bound<'_, PyAny> = this.connection.bind(py).clone(); drop(this); call_with( - &imported(py, module, name)?, + &host::entry(py, GET_COLUMNS_FOR_INSPECTION)?, &[ ("adapter", adapter), ("connection", connection), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/buildability.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/buildability.rs index ae3394bacb..0f02cc0ce8 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/buildability.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/buildability.rs @@ -3,6 +3,7 @@ use crate::bindings::_helpers::planner_phases::constants::MISSING_UPSTREAM_CODE; use crate::bindings::_helpers::planner_phases::objects::{frozenset, native, planner_error}; use crate::bindings::_helpers::planner_warehouse::source_tables::check_selected_source_tables_exist; +use crate::bindings::_helpers::records::ids::NativeAttr; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::pyfunction; @@ -26,7 +27,7 @@ pub(crate) fn check_selected_scope_buildability<'py>( } let missing = native( py, - "check_buildability", + NativeAttr::CheckBuildability, &[ ("selected_keys", selected_scope.getattr("selected_keys")?), ("upstream_deps", selected_scope.getattr("upstream_deps")?), @@ -41,14 +42,14 @@ pub(crate) fn check_selected_scope_buildability<'py>( if missing.is_truthy()? { let message: String = native( py, - "missing_upstream_message", + NativeAttr::MissingUpstreamMessage, &[ ("missing", missing), ( "edge_origins", native( py, - "build_execution_edge_origins", + NativeAttr::BuildExecutionEdgeOrigins, &[("project", project.clone())], )?, ), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/constants.rs index 957bc533a0..5c45d15791 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/constants.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/constants.rs @@ -1,12 +1,5 @@ //! Python modules and names the planner phases exchange values with. -pub(crate) const NATIVE_MODULE: &str = "sqlbuild._native"; -pub(crate) const PLANNER_MODELS: &str = "sqlbuild._native"; -pub(crate) const PLANNER_TYPES: &str = "sqlbuild._native"; -pub(crate) const PLANNER_EXCEPTIONS: &str = "sqlbuild._native"; -pub(crate) const ADAPTER_MODELS: &str = "sqlbuild.adapter.contract.models"; -pub(crate) const ADAPTER_TYPES: &str = "sqlbuild.adapter.contract.types"; -pub(crate) const SPEC_MODELS: &str = "sqlbuild._native"; pub(crate) const SNOWFLAKE_ADAPTER: &str = "snowflake"; pub(crate) const BIGQUERY_ADAPTER: &str = "bigquery"; pub(crate) const TRANSIENT_TABLE_TYPE: &str = "transient"; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/execution_changes.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/execution_changes.rs index df59a1c784..0b13e121f6 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/execution_changes.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/execution_changes.rs @@ -1,9 +1,10 @@ //! Execution-scope pruning, write-hash reconciliation and plan entry construction. use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; -use crate::bindings::_helpers::planner_phases::constants::NATIVE_MODULE; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::planner_phases::objects::native; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::IntoPyObject; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::pyfunction; @@ -20,7 +21,7 @@ pub(crate) fn prune_planner_execution_scope<'py>( let inspection = scopes.getattr("inspection_scope")?; native( py, - "PlannerScopePruningResult", + RecordId::PlannerScopePruningResult, &[ ("inspection_scope", inspection.clone()), ("execution_scope", inspection), @@ -45,7 +46,7 @@ pub(crate) fn reconcile_execution_changes<'py>( let py = warehouse.py(); let honest = native( py, - "with_honest_model_write_hashes", + NativeAttr::WithHonestModelWriteHashes, &[ ("scope", pruning.getattr("execution_scope")?), ("snapshot", warehouse.getattr("snapshot")?), @@ -58,7 +59,7 @@ pub(crate) fn reconcile_execution_changes<'py>( )?; let resolved = native( py, - "merge_recomputed_model_changes", + NativeAttr::MergeRecomputedModelChanges, &[ ("resolved_actions", pruning.getattr("resolved_actions")?), ("changes", honest.clone()), @@ -66,7 +67,7 @@ pub(crate) fn reconcile_execution_changes<'py>( )?; native( py, - "PlannerChangeReconciliation", + RecordId::PlannerChangeReconciliation, &[("changes", honest), ("resolved_actions", resolved)], ) } @@ -127,7 +128,7 @@ pub(crate) fn build_planner_entry_results<'py>( let config = runtime.getattr("project_config")?; let execution_scope = pruning.getattr("execution_scope")?; let relations = call_with( - &imported(py, NATIVE_MODULE, "build_planner_relations_context")?, + ®istry::native(py, NativeAttr::BuildPlannerRelationsContext)?, &[ ("project", project.clone()), ("adapter", runtime.getattr("adapter")?), @@ -158,7 +159,7 @@ pub(crate) fn build_planner_entry_results<'py>( max_override.clone() }; let limit_action = if config.is_none() { - native(py, "PlanEntryBuildInputs", &[])?.getattr("microbatch_limit_action")? + native(py, RecordId::PlanEntryBuildInputs, &[])?.getattr("microbatch_limit_action")? } else { config .getattr("microbatches")? @@ -167,7 +168,7 @@ pub(crate) fn build_planner_entry_results<'py>( }; let build_inputs = native( py, - "PlanEntryBuildInputs", + RecordId::PlanEntryBuildInputs, &[ ("source_freshness_blocked_model_names", blocked), ( @@ -202,7 +203,7 @@ pub(crate) fn build_planner_entry_results<'py>( ], )?; let entries = call_with( - &imported(py, NATIVE_MODULE, "build_plan_entries")?, + ®istry::native(py, NativeAttr::BuildPlanEntries)?, &[ ("project", project), ("adapter", runtime.getattr("adapter")?), @@ -220,7 +221,7 @@ pub(crate) fn build_planner_entry_results<'py>( )?; native( py, - "PlannerEntryResults", + RecordId::PlannerEntryResults, &[("model_entry_results", entries)], ) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/identities.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/identities.rs index 1eaffa57e3..be031788b3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/identities.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/identities.rs @@ -2,6 +2,7 @@ use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; use crate::bindings::_helpers::planner_phases::objects::{frozenset, native, replace}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use pyo3::IntoPyObject; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::pyfunction; @@ -15,7 +16,7 @@ fn version_identities<'py>( ) -> PyResult> { native( project.py(), - "build_direct_model_version_identities", + NativeAttr::BuildDirectModelVersionIdentities, &[ ("functions", project.getattr("functions")?), ("seeds", project.getattr("seeds")?), @@ -41,7 +42,7 @@ pub(crate) fn build_planner_identity_context<'py>( }; native( project.py(), - "PlannerIdentityContext", + RecordId::PlannerIdentityContext, &[ ("version_identities", identities), ("stale_warning_identities", stale), @@ -128,7 +129,7 @@ pub(crate) fn detect_stale_warning_changes<'py>( let stale_identities = identities.getattr("stale_warning_identities")?; let remaining_changes = native( py, - "detect_model_changes_in_scope", + NativeAttr::DetectModelChangesInScope, &[ ("project", project.clone()), ( @@ -168,7 +169,7 @@ pub(crate) fn detect_stale_warning_changes<'py>( } native( py, - "PlannerChangeResults", + RecordId::PlannerChangeResults, &[ ("models", models.into_any()), ("functions", PyDict::new(py).into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/objects.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/objects.rs index cce89afb2c..dece55e662 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/objects.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/objects.rs @@ -1,9 +1,10 @@ //! Boundary helpers shared by the native planner phases. use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; -use crate::bindings::_helpers::planner_phases::constants::{ - NATIVE_MODULE, PLANNER_EXCEPTIONS, PLANNER_MODELS, -}; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::models::HostId; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, NativeItem, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python}; use pyo3::types::{PyFrozenSet, PyString, PyTuple}; @@ -17,31 +18,31 @@ pub(crate) fn optional_text<'py>(py: Python<'py>, value: Option<&str>) -> Bound< value.map_or_else(|| py.None().into_bound(py), |value| text(py, value)) } -/// `module.name(**fields)`. +/// The staying-Python class `class(**fields)`. pub(crate) fn construct<'py>( py: Python<'py>, - (module, name): (&str, &str), + class: impl Into, fields: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - call_with(&imported(py, module, name)?, fields) + call_with(&host::entry(py, class)?, fields) } /// A planner model dataclass. pub(crate) fn planner_model<'py>( py: Python<'py>, - name: &str, + name: RecordId, fields: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - construct(py, (PLANNER_MODELS, name), fields) + call_with(®istry::class(py, name)?, fields) } /// `sqlbuild._native.name(**fields)`. pub(crate) fn native<'py>( py: Python<'py>, - name: &str, + name: impl Into, fields: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - construct(py, (NATIVE_MODULE, name), fields) + call_with(®istry::item(py, name)?, fields) } /// `dataclasses.replace(value, **fields)`. @@ -53,13 +54,22 @@ pub(crate) fn replace<'py>( .call((value,), Some(&keywords(value.py(), fields)?)) } -/// A Python enum member from its value. +/// A staying-Python enum member from its value. pub(crate) fn member<'py>( py: Python<'py>, - (module, name): (&str, &str), + class: impl Into, value: &str, ) -> PyResult> { - imported(py, module, name)?.call1((value,)) + host::entry(py, class)?.call1((value,)) +} + +/// A native string enum member from its value. +pub(crate) fn native_member<'py>( + py: Python<'py>, + class: RecordId, + value: &str, +) -> PyResult> { + registry::class(py, class)?.call1((value,)) } /// `PlannerInputError(message, code=code, help=help)`. @@ -68,7 +78,7 @@ pub(crate) fn planner_error( message: &str, (code, help): (Option<&str>, Option<&str>), ) -> PyErr { - let raised = imported(py, PLANNER_EXCEPTIONS, "PlannerInputError").and_then(|class| { + let raised = registry::error(py, ErrorId::PlannerInput).and_then(|class| { let fields = keywords( py, &[ @@ -146,7 +156,7 @@ pub(crate) fn by_name<'py>( pub(crate) fn config_str(model: &Bound<'_, PyAny>, key: &str) -> PyResult> { native( model.py(), - "get_config_str", + NativeAttr::GetConfigStr, &[ ("values", model.getattr("config")?.getattr("values")?), ("key", text(model.py(), key)), @@ -161,7 +171,7 @@ pub(crate) fn model_key<'py>( name: &Bound<'py, PyAny>, ) -> PyResult> { call_with( - &imported(py, "sqlbuild._native", "CompiledObjectKey")?, + ®istry::class(py, RecordId::CompiledObjectKey)?, &[ ( "resource_type", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/output_assembly.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/output_assembly.rs index 38700ac347..5ce7e353c0 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/output_assembly.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/output_assembly.rs @@ -1,12 +1,13 @@ //! Plan output assembly: storage policies, warnings and direct-mode metadata. use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; -use crate::bindings::_helpers::planner_phases::constants::NATIVE_MODULE; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::planner_phases::objects::{ model_key_names, native, replace, sorted_names, strings, text, tuple, }; use crate::bindings::_helpers::planner_warehouse::source_freshness::source_freshness_unknown_warnings; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::pyfunction; use pyo3::types::PyDict as KeywordDict; @@ -78,7 +79,7 @@ pub(crate) fn assemble_base_plan_output<'py>( let version = identities.getattr("version_identities")?; let extras = native( py, - "PlanOutputExtras", + RecordId::PlanOutputExtras, &[ ( "seed_version_hashes", @@ -92,7 +93,7 @@ pub(crate) fn assemble_base_plan_output<'py>( ], )?; let plan_output = call_with( - &imported(py, NATIVE_MODULE, "build_plan_output")?, + ®istry::native(py, NativeAttr::BuildPlanOutput)?, &[ ("project", runtime.getattr("project")?), ("adapter", runtime.getattr("adapter")?), @@ -191,7 +192,7 @@ pub(crate) fn with_storage_policies<'py>( } entries = native( py, - "with_migration_fingerprints", + NativeAttr::WithMigrationFingerprints, &[ ("entries", entries), ("models_by_name", models.into_any()), @@ -223,8 +224,8 @@ pub(crate) fn with_storage_policies<'py>( ("warehouse", warehouse.clone()), ("scope", selected), ]; - let table_types = native(py, "plan_table_types", &storage)?; - let retention = native(py, "plan_retention", &storage)?; + let table_types = native(py, NativeAttr::PlanTableTypes, &storage)?; + let retention = native(py, NativeAttr::PlanRetention, &storage)?; replace( plan_output, &[ @@ -287,7 +288,7 @@ pub(crate) fn with_plan_warnings<'py>( } let stale = native( py, - "build_stale_out_of_selection_warnings", + NativeAttr::BuildStaleOutOfSelectionWarnings, &[ ("original_scope", scopes.getattr("stale_warning_scope")?), ("execution_scope", pruning.getattr("execution_scope")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/retention.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/retention.rs index c7ff944b97..ff3d379738 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/retention.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/retention.rs @@ -1,15 +1,19 @@ //! Warehouse-aware time-travel retention and Snowflake table-type planning. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_phases::constants::{ - ADAPTER_MODELS, ADAPTER_TYPES, BIGQUERY_ADAPTER, DECREASE_WARNING, DOWNGRADE_HISTORY_DAYS, - PERMANENT_TABLE_TYPE, PLANNER_TYPES, RETENTION_DECREASE, RETENTION_INCREASE, RETENTION_MATCH, - SNOWFLAKE_ADAPTER, SPEC_MODELS, TRANSIENT_TABLE_TYPE, + BIGQUERY_ADAPTER, DECREASE_WARNING, DOWNGRADE_HISTORY_DAYS, PERMANENT_TABLE_TYPE, + RETENTION_DECREASE, RETENTION_INCREASE, RETENTION_MATCH, SNOWFLAKE_ADAPTER, + TRANSIENT_TABLE_TYPE, }; use crate::bindings::_helpers::planner_phases::objects::{ - construct, member, model_key_names, native, optional_text, planner_error, planner_model, - replace, sorted_names, text, tuple, + construct, member, model_key_names, native, native_member, optional_text, planner_error, + planner_model, replace, sorted_names, text, tuple, }; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::IntoPyObject; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::pyfunction; @@ -31,11 +35,11 @@ pub(crate) fn effective_target_config<'py>( .getattr("project")? .getattr("effective_target_name")?; if project_config.is_none() || local_config.is_none() || target_name.is_none() { - return construct(py, (SPEC_MODELS, "TargetConfig"), &[]); + return native(py, RecordId::TargetConfig, &[]); } native( py, - "resolve_target_config", + NativeAttr::ResolveTargetConfig, &[ ("project_config", project_config), ("local_config", local_config), @@ -115,7 +119,7 @@ pub(crate) fn plan_table_types<'py>( .getattr("snapshot")? .getattr("existing_relations")?; let adapter = runtime.getattr("adapter")?; - let materialization = imported(py, PLANNER_TYPES, "MaterializationType")?; + let materialization = registry::class(py, RecordId::MaterializationType)?; let mut entries: Vec<_> = Vec::new(); for model in models { let config = model.getattr("config")?; @@ -154,7 +158,7 @@ pub(crate) fn plan_table_types<'py>( let target_name: String = destination.getattr("name")?.extract()?; entries.push(planner_model( py, - "TableTypePlanEntry", + RecordId::TableTypePlanEntry, &[ ("model_name", model.getattr("name")?), ("destination", destination), @@ -218,7 +222,7 @@ fn entry<'py>( }; planner_model( py, - "RetentionPlanEntry", + RecordId::RetentionPlanEntry, &[ ("request", base.request.clone()), ("model_names", base.model_names.clone()), @@ -227,11 +231,11 @@ fn entry<'py>( ("source", base.source.clone()), ( "direction", - member(py, (PLANNER_TYPES, "RetentionDirection"), direction)?, + native_member(py, RecordId::RetentionDirection, direction)?, ), ( "phase", - member(py, (PLANNER_TYPES, "RetentionPlanPhase"), phase)?, + native_member(py, RecordId::RetentionPlanPhase, phase)?, ), ("statements", statements), ( @@ -335,12 +339,12 @@ fn relation_retention<'py>( } let request = construct( py, - (ADAPTER_MODELS, "RetentionRequest"), + AdapterHost::RetentionRequest, &[ ("request_id", model.getattr("name")?), ( "scope", - member(py, (ADAPTER_TYPES, "RetentionScope"), "relation")?, + member(py, AdapterHost::RetentionScope, "relation")?, ), ("database", destination.getattr("database")?), ("schema", schema.clone()), @@ -373,7 +377,7 @@ fn relation_retention<'py>( let relation = relations.get_item(model.getattr("name")?)?; let relation_transient = relation.getattr("is_transient")?; let lookup = call_with( - &imported(py, ADAPTER_MODELS, "RelationLookup")?.getattr("key")?, + &host::entry(py, AdapterHost::RelationLookup)?.getattr("key")?, &[ ("database", destination.getattr("database")?), ("schema", schema), @@ -526,12 +530,12 @@ fn bigquery_retention<'py>( let desired_days = desired_values.into_iter().next().unwrap_or_default(); let request = construct( py, - (ADAPTER_MODELS, "RetentionRequest"), + AdapterHost::RetentionRequest, &[ ("request_id", text(py, &format!("{prefix}{schema}"))), ( "scope", - member(py, (ADAPTER_TYPES, "RetentionScope"), "namespace")?, + member(py, AdapterHost::RetentionScope, "namespace")?, ), ("database", optional_text(py, database.as_deref())), ("schema", text(py, &schema)), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/scopes.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/scopes.rs index 5e4f8c23e6..4338918063 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/scopes.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/scopes.rs @@ -1,6 +1,7 @@ //! Resolve the selected, stale-warning and inspection scopes of one plan. use crate::bindings::_helpers::planner_phases::objects::{native, replace, tuple}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use pyo3::IntoPyObject; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::pyfunction; @@ -17,7 +18,7 @@ pub(crate) fn resolve_planner_scopes<'py>( let auto_load = policies.getattr("auto_load_sources")?; let selected = native( py, - "build_planner_scope", + NativeAttr::BuildPlannerScope, &[ ("project", project.clone()), ("select", selection.getattr("select")?), @@ -37,7 +38,7 @@ pub(crate) fn resolve_planner_scopes<'py>( let empty = PyTuple::empty(py).into_any(); let full = native( py, - "build_planner_scope", + NativeAttr::BuildPlannerScope, &[ ("project", project.clone()), ("select", empty.clone()), @@ -47,7 +48,7 @@ pub(crate) fn resolve_planner_scopes<'py>( )?; let stale_keys = native( py, - "transitive_closure_many", + NativeAttr::TransitiveClosureMany, &[ ("starts", selected.getattr("selected_keys")?), ("edges", full.getattr("upstream_deps")?), @@ -91,7 +92,7 @@ pub(crate) fn resolve_planner_scopes<'py>( )?; native( py, - "PlannerScopeResolution", + RecordId::PlannerScopeResolution, &[ ("selected_scope", selected.clone()), ("stale_warning_scope", stale_scope), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/warehouse_state.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/warehouse_state.rs index 632375212c..1ac5f15e7b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/warehouse_state.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/warehouse_state.rs @@ -1,11 +1,12 @@ //! The warehouse inspection phase: snapshot, migrations and inspection relations in one pass. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; -use crate::bindings::_helpers::planner_phases::constants::NATIVE_MODULE; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::planner_phases::full_refresh::effectively_full_refreshed_model_names; use crate::bindings::_helpers::planner_phases::objects::native; use crate::bindings::_helpers::planner_warehouse::fingerprint_cache::MigrationFingerprintCache; use crate::bindings::_helpers::planner_warehouse::snapshot::{SnapshotRequest, warehouse_snapshot}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyResult}; use pyo3::pyfunction; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; @@ -42,7 +43,7 @@ pub(crate) fn gather_planner_warehouse_state<'py>( let config = runtime.getattr("project_config")?; let cursor_scope = native( py, - "CursorSnapshotScope", + RecordId::CursorSnapshotScope, &[ ("model_keys", selected_scope.getattr("selected_keys")?), ( @@ -86,7 +87,7 @@ pub(crate) fn gather_planner_warehouse_state<'py>( })?; let migrations = native( py, - "plan_model_migrations", + NativeAttr::PlanModelMigrations, &[ ("runtime", runtime.clone()), ("scope", selected_scope.clone()), @@ -98,7 +99,7 @@ pub(crate) fn gather_planner_warehouse_state<'py>( )?; fingerprints.call_method0("persist")?; let inspection_relations = call_with( - &imported(py, NATIVE_MODULE, "build_planner_relations_context")?, + ®istry::native(py, NativeAttr::BuildPlannerRelationsContext)?, &[ ("project", project.clone()), ("adapter", adapter.clone()), @@ -131,7 +132,7 @@ pub(crate) fn gather_planner_warehouse_state<'py>( } let columns = native( py, - "plan_column_migrations", + NativeAttr::PlanColumnMigrations, &[ ("runtime", runtime.clone()), ("scope", selected_scope.clone()), @@ -151,7 +152,7 @@ pub(crate) fn gather_planner_warehouse_state<'py>( )?; let old_names = native( py, - "plan_old_name_views", + NativeAttr::PlanOldNameViews, &[ ("runtime", runtime.clone()), ("scope", selected_scope), @@ -178,7 +179,7 @@ pub(crate) fn gather_planner_warehouse_state<'py>( } native( py, - "PlannerWarehouseState", + RecordId::PlannerWarehouseState, &[ ("snapshot", columns.getattr("snapshot")?), ("inspection_relations", inspection_relations), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_records/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_records/constants.rs index 9d4703965a..6bd7cb4f40 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_records/constants.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_records/constants.rs @@ -1,7 +1,9 @@ //! Layouts of the planner, migration, cursor and fingerprint value records hosted natively. use crate::bindings::_helpers::build_command::support::utc_now; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::record_behaviours::planner::forward_only_backfill; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::models::{EnumSpec, FieldDefault, FieldSpec, RecordSpec}; const R_PARSED_SELECTOR: RecordSpec = RecordSpec { @@ -47,7 +49,7 @@ const R_PLANNER_SCOPE: RecordSpec = RecordSpec { FieldSpec::new("python_read_source_names", FieldDefault::EmptyFrozenset), FieldSpec::new( "sql_test_selection", - FieldDefault::Call("sqlbuild._native", "SqlTestSelection"), + FieldDefault::NativeCall(RecordId::SqlTestSelection), ), ], }; @@ -86,7 +88,7 @@ const R_PLANNER_SELECTION: RecordSpec = RecordSpec { FieldSpec::new("python_read_source_names", FieldDefault::EmptyFrozenset), FieldSpec::new( "sql_test_selection", - FieldDefault::Call("sqlbuild._native", "SqlTestSelection"), + FieldDefault::NativeCall(RecordId::SqlTestSelection), ), ], }; @@ -359,7 +361,7 @@ const R_MODEL_CURSOR_SNAPSHOT: RecordSpec = RecordSpec { FieldSpec::new("unavailable_watermark_tags", FieldDefault::EmptyTuple), FieldSpec::new( "cursor_watermark_mode", - FieldDefault::Member("sqlbuild._native", "CursorWatermarkMode", "ALL"), + FieldDefault::NativeMember(RecordId::CursorWatermarkMode, "ALL"), ), FieldSpec::new("upstream_terminal_starts", FieldDefault::EmptyTuple), FieldSpec::new("upstream_terminal_ends", FieldDefault::EmptyTuple), @@ -525,7 +527,7 @@ const R_MODEL_MIGRATION_PLAN_ENTRY: RecordSpec = RecordSpec { FieldSpec::new("origin_tracked", FieldDefault::False), FieldSpec::new( "missing_origin_policy", - FieldDefault::Member("sqlbuild._native", "MissingMigrationOriginPolicy", "ALLOW"), + FieldDefault::NativeMember(RecordId::MissingMigrationOriginPolicy, "ALLOW"), ), ], }; @@ -579,7 +581,7 @@ const R_COLUMN_MIGRATION_PLAN_ENTRY: RecordSpec = RecordSpec { FieldSpec::new("message", FieldDefault::None), FieldSpec::new( "missing_origin_policy", - FieldDefault::Member("sqlbuild._native", "MissingMigrationOriginPolicy", "ALLOW"), + FieldDefault::NativeMember(RecordId::MissingMigrationOriginPolicy, "ALLOW"), ), ], }; @@ -691,11 +693,11 @@ const R_SEED_PLAN_ENTRY: RecordSpec = RecordSpec { FieldSpec::new("fingerprint_metadata_json", FieldDefault::Text("{}")), FieldSpec::new( "action", - FieldDefault::Member("sqlbuild._native", "PlanAction", "LOAD_SEED"), + FieldDefault::NativeMember(RecordId::PlanAction, "LOAD_SEED"), ), FieldSpec::new( "reason", - FieldDefault::Member("sqlbuild._native", "PlanReason", "FIRST_RUN"), + FieldDefault::NativeMember(RecordId::PlanReason, "FIRST_RUN"), ), ], }; @@ -709,11 +711,7 @@ const R_SOURCE_LOAD_PLAN_ENTRY: RecordSpec = RecordSpec { FieldSpec::required("destination"), FieldSpec::new( "resource_kind", - FieldDefault::Member( - "sqlbuild.runtime.contracts.types", - "ExecutionResourceKind", - "SOURCE", - ), + FieldDefault::HostMember(ExecutorHost::ExecutionResourceKind.id(), "SOURCE"), ), FieldSpec::new("write_strategy", FieldDefault::None), FieldSpec::new("cursor_column", FieldDefault::None), @@ -747,7 +745,7 @@ const R_FUNCTION_PLAN_ENTRY: RecordSpec = RecordSpec { FieldSpec::new("previous_query_sql", FieldDefault::None), FieldSpec::new( "reason", - FieldDefault::Member("sqlbuild._native", "PlanReason", "NO_CHANGE"), + FieldDefault::NativeMember(RecordId::PlanReason, "NO_CHANGE"), ), ], }; @@ -1042,7 +1040,7 @@ const R_RELATION_FIXTURE_PLANNING_CONTEXT: RecordSpec = RecordSpec { FieldSpec::required("expected_types"), FieldSpec::uncompared_shown( "fixture_inferences", - FieldDefault::Call("sqlbuild._native", "FixtureColumnInferences"), + FieldDefault::NativeClassCall(NativeAttr::FixtureColumnInferences), ), ], }; @@ -1112,7 +1110,7 @@ const R_WAREHOUSE_SNAPSHOT: RecordSpec = RecordSpec { FieldSpec::new("existing_columns", FieldDefault::EmptyDict), FieldSpec::new( "fingerprints", - FieldDefault::Call("sqlbuild._native", "WarehouseFingerprints"), + FieldDefault::NativeCall(RecordId::WarehouseFingerprints), ), FieldSpec::new("cursor_snapshots", FieldDefault::EmptyDict), FieldSpec::new( @@ -1239,7 +1237,7 @@ const R_PLAN_ENTRY_BUILD_INPUTS: RecordSpec = RecordSpec { FieldSpec::new("max_microbatches_is_override", FieldDefault::False), FieldSpec::new( "microbatch_limit_action", - FieldDefault::Member("sqlbuild._native", "MicrobatchLimitAction", "ERROR"), + FieldDefault::NativeMember(RecordId::MicrobatchLimitAction, "ERROR"), ), ], }; @@ -1250,7 +1248,7 @@ const R_FUNCTION_CHANGE_RESULT: RecordSpec = RecordSpec { FieldSpec::required("fingerprint_sql"), FieldSpec::new( "reason", - FieldDefault::Member("sqlbuild._native", "PlanReason", "NO_CHANGE"), + FieldDefault::NativeMember(RecordId::PlanReason, "NO_CHANGE"), ), ], }; @@ -1364,7 +1362,7 @@ const R_PLANNED_SQL_TESTS: RecordSpec = RecordSpec { FieldSpec::required("selected_keys"), FieldSpec::new( "sql_test_selection", - FieldDefault::Call("sqlbuild._native", "SqlTestSelection"), + FieldDefault::NativeCall(RecordId::SqlTestSelection), ), FieldSpec::new("entries", FieldDefault::EmptyTuple), FieldSpec::new("warnings", FieldDefault::EmptyTuple), @@ -1472,7 +1470,7 @@ const R_PLANNER_WAREHOUSE_STATE: RecordSpec = RecordSpec { FieldSpec::new("old_name_views", FieldDefault::EmptyTuple), FieldSpec::new( "migration_fingerprints", - FieldDefault::Call("sqlbuild._native", "MigrationFingerprintCache"), + FieldDefault::NativeClassCall(NativeAttr::MigrationFingerprintCache), ), ], }; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_bounds.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_bounds.rs index 1daebfe0ac..fa27998cc4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_bounds.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_bounds.rs @@ -1,11 +1,13 @@ //! Snapshot conversion and cursor window policy boundary. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::cursor::conversion::{CursorSession, algebra_error}; use crate::bindings::_helpers::cursor::scalars::try_parse; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_resolution::values::string; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::PyDict; use pyo3::{IntoPyObject, pyfunction, wrap_pyfunction}; @@ -15,9 +17,6 @@ use sqlbuild_cursor::planner::main::raw_bounds::raw_bounds; use sqlbuild_cursor::planner::main::replay_start::replay_start; use sqlbuild_cursor::planner::models::{CursorKind, CursorSnapshot, ReplayPolicy}; -const MODELS: &str = "sqlbuild._native"; -const MAXIMUM_START: &str = "sqlbuild._native"; - pub(super) fn optional_scalar<'py>( session: &CursorSession<'py>, value: &Bound<'py, PyAny>, @@ -69,7 +68,7 @@ pub(super) fn duration(value: &Bound<'_, PyAny>) -> PyResult( &["cursor_grain", "maximum_start_policy"], ), )?; - let bounds_class = imported(py, MODELS, "CursorBounds")?; + let bounds_class = registry::class(py, RecordId::CursorBounds)?; if args.flag("is_microbatch")? { - let sentinel = imported(py, "sqlbuild.cursor_algebra.types", "BoundSentinel")?; + let sentinel = host::entry(py, ExecutorHost::BoundSentinel)?; return call_with( &bounds_class, &[ @@ -259,7 +258,7 @@ fn compute_cursor_bounds<'py>( ), ]); let replayed = call_with( - &imported(py, "sqlbuild._native", "apply_typed_cursor_replay_policy")?, + ®istry::native(py, NativeAttr::ApplyTypedCursorReplayPolicy)?, &replay_args, )?; let bounds = call_with( @@ -270,7 +269,7 @@ fn compute_cursor_bounds<'py>( let policy = if policy.is_truthy()? { policy } else { - imported(py, MODELS, "MaximumStartPolicyInputs")?.call0()? + registry::class(py, RecordId::MaximumStartPolicyInputs)?.call0()? }; let mut policy_args = vec![ ("bounds", bounds), @@ -294,7 +293,7 @@ fn compute_cursor_bounds<'py>( policy_args.push((key, args.value(key)?)); } call_with( - &imported(py, MAXIMUM_START, "apply_maximum_start_policy")?, + ®istry::native(py, NativeAttr::ApplyMaximumStartPolicy)?, &policy_args, ) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_model.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_model.rs index c9d3b069c1..45114be861 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_model.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_model.rs @@ -1,29 +1,28 @@ //! Model cursor orchestration over live warehouse snapshot facts. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_resolution::constants::CURSOR_START; use crate::bindings::_helpers::planner_resolution::cursor_policies::{ future_cursor_config, start_cursor_config, }; -use crate::bindings::_helpers::planner_resolution::values::{NATIVE_MODULE, string}; +use crate::bindings::_helpers::planner_resolution::values::string; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::PyDict; use pyo3::{IntoPyObject, PyErr, pyfunction, wrap_pyfunction}; -const MODELS: &str = "sqlbuild._native"; -const CURSOR: &str = "sqlbuild._native"; - fn config<'py>(model: &Bound<'py, PyAny>, key: &str) -> PyResult> { call_with( - &imported( + ®istry::native( model.py(), - NATIVE_MODULE, if key == CURSOR_START { - "get_config_cursor_bound" + NativeAttr::GetConfigCursorBound } else { - "get_config_str" + NativeAttr::GetConfigStr }, )?, &[ @@ -34,9 +33,9 @@ fn config<'py>(model: &Bound<'py, PyAny>, key: &str) -> PyResult) -> PyResult> { - let sentinel = imported(py, "sqlbuild.cursor_algebra.types", "BoundSentinel")?; + let sentinel = host::entry(py, ExecutorHost::BoundSentinel)?; call_with( - &imported(py, MODELS, "CursorBounds")?, + ®istry::class(py, RecordId::CursorBounds)?, &[ ("start", sentinel.getattr("START")?), ("end", sentinel.getattr("END")?), @@ -53,12 +52,7 @@ fn destination_exists(model: &Bound<'_, PyAny>, snapshot: &Bound<'_, PyAny>) -> } let destination = model.getattr("destination")?; let key = call_with( - &imported( - model.py(), - "sqlbuild.adapter.contract.models", - "RelationLookup", - )? - .getattr("key")?, + &host::entry(model.py(), AdapterHost::RelationLookup)?.getattr("key")?, &[ ("database", destination.getattr("database")?), ("schema", destination.getattr("schema")?), @@ -114,7 +108,7 @@ fn compute_model_cursor_bounds<'py>( let start_override = args.value("start_cursor_override")?; let end_override = args.value("end_cursor_override")?; let bounded = call_with( - &imported(py, NATIVE_MODULE, "resolve_bounded_cursor_override")?, + ®istry::native(py, NativeAttr::ResolveBoundedCursorOverride)?, &[ ("start_cursor_override", start_override.clone()), ("end_cursor_override", end_override.clone()), @@ -130,7 +124,7 @@ fn compute_model_cursor_bounds<'py>( .getattr("cursor_snapshots")? .call_method1("get", (model.getattr("name")?,))?; let roles = call_with( - &imported(py, NATIVE_MODULE, "resolve_cursor_input_roles")?, + ®istry::native(py, NativeAttr::ResolveCursorInputRoles)?, &[("model", model.clone())], )?; let producers = args.value("runtime_cursor_producer_names")?; @@ -145,7 +139,7 @@ fn compute_model_cursor_bounds<'py>( let replaces = args.flag("replaces_relation")?; if !full_refresh && !runtime_owned && !cursor_snapshot.is_none() { let empty = call_with( - &imported(py, CURSOR, "cursor_inputs_without_rows")?, + ®istry::native(py, NativeAttr::CursorInputsWithoutRows)?, &[("cursor_snapshot", cursor_snapshot.clone())], )? .getattr("input_names")?; @@ -155,7 +149,7 @@ fn compute_model_cursor_bounds<'py>( || history_redirected(&model, &cursor_snapshot)?) { let refusal = call_with( - &imported(py, CURSOR, "empty_input_rebuild_refusal")?, + ®istry::native(py, NativeAttr::EmptyInputRebuildRefusal)?, &[ ("model_name", model.getattr("name")?), ("input_names", empty.clone()), @@ -163,7 +157,7 @@ fn compute_model_cursor_bounds<'py>( ], )?; return Err(PyErr::from_value(call_with( - &imported(py, "sqlbuild._native", "PlannerInputError")?, + ®istry::error(py, ErrorId::PlannerInput)?, &[ ("message", refusal.get_item(0)?), ("code", string(py, "S302")), @@ -173,7 +167,7 @@ fn compute_model_cursor_bounds<'py>( } if empty.is_truthy()? { return call_with( - &imported(py, CURSOR, "empty_cursor_window")?, + ®istry::native(py, NativeAttr::EmptyCursorWindow)?, &[ ("cursor_type", cursor_type), ("cursor_start", config(&model, "cursor_start")?), @@ -191,7 +185,7 @@ fn compute_model_cursor_bounds<'py>( cursor_snapshot.getattr("unavailable_watermark_tags")? }; return Err(PyErr::from_value(call_with( - &imported(py, CURSOR, "unreadable_cursor_inputs_error")?, + ®istry::native(py, NativeAttr::UnreadableCursorInputsError)?, &[ ("model_name", model.getattr("name")?), ("cursor_type", cursor_type), @@ -211,7 +205,7 @@ fn compute_model_cursor_bounds<'py>( } if replaces { cursor_snapshot = call_with( - &imported(py, CURSOR, "without_destination_cursor")?, + ®istry::native(py, NativeAttr::WithoutDestinationCursor)?, &[("cursor_snapshot", cursor_snapshot)], )?; } @@ -222,7 +216,7 @@ fn compute_model_cursor_bounds<'py>( py.None().into_bound(py) }; let policy = call_with( - &imported(py, MODELS, "MaximumStartPolicyInputs")?, + ®istry::class(py, RecordId::MaximumStartPolicyInputs)?, &[ ("config", start_config), ("invocation_time", context.getattr("invocation_time")?), @@ -234,7 +228,7 @@ fn compute_model_cursor_bounds<'py>( ], )?; let bounds = call_with( - &imported(py, NATIVE_MODULE, "compute_cursor_bounds")?, + ®istry::native(py, NativeAttr::ComputeCursorBounds)?, &[ ("cursor_snapshot", cursor_snapshot.clone()), ("cursor_type", cursor_type.clone()), @@ -255,7 +249,7 @@ fn compute_model_cursor_bounds<'py>( return Ok(bounds); } call_with( - &imported(py, NATIVE_MODULE, "resolve_future_cursor_safety")?, + ®istry::native(py, NativeAttr::ResolveFutureCursorSafety)?, &[ ("bounds", bounds), ("cursor_type", cursor_type), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_overrides.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_overrides.rs index cdc86a32b2..ca59b2a270 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_overrides.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_overrides.rs @@ -1,9 +1,9 @@ //! Selected-model inspection and untyped override conversion. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::planner_resolution::values::{method, string}; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{ Bound, PyAny, PyAnyMethods, PyErr, PyModule, PyModuleMethods, PyResult, Python, }; @@ -27,7 +27,7 @@ fn type_cursor_overrides<'py>( if overrides.is_none() || !overrides.getattr("has_untyped")?.is_truthy()? { return Ok(overrides); } - let get_config = imported(py, "sqlbuild._native", "get_config_str")?; + let get_config = registry::native(py, NativeAttr::GetConfigStr)?; let mut models: Vec<(String, String)> = Vec::new(); let selected = if args.passed("selected_models")? { args.value("selected_models")? @@ -68,12 +68,12 @@ fn type_cursor_overrides<'py>( } } let kind = override_kind(&models, integer_literals).map_err(|message| { - match imported(py, "sqlbuild._native", "PlannerInputError").and_then(|class| call_with(&class, &[ + match registry::error(py, ErrorId::PlannerInput).and_then(|class| call_with(&class, &[ ("message", string(py, &message)), ("code", string(py, "S303")), ("help", string(py, "select models with one cursor type, or set each type explicitly with --start-cursor-ts/--end-cursor-ts and --start-cursor-int/--end-cursor-int")), ])) { Ok(error) => PyErr::from_value(error), Err(error) => error } })?; - let kind = imported(py, "sqlbuild._native", "CursorType")?.call1((kind,))?; + let kind = registry::class(py, RecordId::CursorType)?.call1((kind,))?; method(&overrides, "typed_as", &[("cursor_type", kind)]) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_policies.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_policies.rs index fd73879b06..8d61ea65dc 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_policies.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_policies.rs @@ -1,31 +1,31 @@ //! Model-level cursor safety policy overrides over project configuration. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult}; use pyo3::types::PyString; use pyo3::{pyfunction, wrap_pyfunction}; -const SPEC_MODELS: &str = "sqlbuild._native"; -const SPEC_TYPES: &str = "sqlbuild._native"; const POLICY_DISABLED: &str = "disabled"; /// The policy class, its distance field, and the model keys overriding distance and action. struct PolicyFields { - class: &'static str, + class: RecordId, distance: &'static str, distance_key: &'static str, action_key: &'static str, } const FUTURE_POLICY: PolicyFields = PolicyFields { - class: "FutureCursorsConfig", + class: RecordId::FutureCursorsConfig, distance: "max_distance", distance_key: "cursor_future_max_distance", action_key: "cursor_future_action", }; const START_POLICY: PolicyFields = PolicyFields { - class: "StartCursorsConfig", + class: RecordId::StartCursorsConfig, distance: "max_ahead", distance_key: "cursor_start_max_ahead", action_key: "cursor_start_max_action", @@ -37,7 +37,7 @@ fn resolve_policy<'py>( fields: &PolicyFields, ) -> PyResult> { let py = model.py(); - let class = imported(py, SPEC_MODELS, fields.class)?; + let class = registry::class(py, fields.class)?; let configured = if project_config.is_truthy()? { project_config.clone() } else { @@ -56,7 +56,7 @@ fn resolve_policy<'py>( } let mut action = configured.getattr("action")?; if raw_action.is_instance_of::() { - action = imported(py, SPEC_TYPES, "FutureCursorAction")?.call1((raw_action,))?; + action = registry::class(py, RecordId::FutureCursorAction)?.call1((raw_action,))?; } if distance.is_none() { return Ok(py.None().into_bound(py)); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_ranges.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_ranges.rs index 9f973de211..97421226ba 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_ranges.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_ranges.rs @@ -6,12 +6,16 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ use crate::bindings::_helpers::cursor::conversion::CursorSession; use crate::bindings::_helpers::cursor::scalars::{parse_value, sentinel_to_token}; use crate::bindings::_helpers::cursor::values::{local_date_time, replaced}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_resolution::cursor_bounds::duration; use crate::bindings::_helpers::planner_resolution::cursor_policies::{ future_cursor_config, start_cursor_config, }; use crate::bindings::_helpers::planner_resolution::cursor_safety::{replace, utc_clock}; -use crate::bindings::_helpers::planner_resolution::values::{NATIVE_MODULE, string}; +use crate::bindings::_helpers::planner_resolution::values::string; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyString, PyTuple}; use pyo3::{IntoPyObject, PyErr, pyfunction, wrap_pyfunction}; @@ -22,10 +26,6 @@ use sqlbuild_cursor::planner::main::rolling_window::rolling_window; use sqlbuild_cursor::planner::models::CursorEndClamp; use std::cmp::Ordering; -const MODELS: &str = "sqlbuild._native"; -const CURSOR_TYPES: &str = "sqlbuild.cursor_algebra.types"; -const CURSOR_MODELS: &str = "sqlbuild._native"; -const COMPILE_EXCEPTIONS: &str = "sqlbuild._native"; const INCREMENTAL: &str = "incremental"; const MICROBATCH: &str = "microbatch"; const ROLLING_WINDOW: &str = "rolling_window"; @@ -65,17 +65,17 @@ pub(crate) struct CursorWindowInputs<'py> { fn native<'py>( py: Python<'py>, - name: &str, + name: NativeAttr, pairs: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - call_with(&imported(py, NATIVE_MODULE, name)?, pairs) + call_with(®istry::native(py, name)?, pairs) } /// One string-valued model configuration entry. pub(crate) fn config_str<'py>(model: &Bound<'py, PyAny>, key: &str) -> PyResult> { native( model.py(), - "get_config_str", + NativeAttr::GetConfigStr, &[ ("values", model.getattr("config")?.getattr("values")?), ("key", string(model.py(), key)), @@ -90,7 +90,7 @@ pub(crate) fn config_bound<'py>( ) -> PyResult> { native( model.py(), - "get_config_cursor_bound", + NativeAttr::GetConfigCursorBound, &[ ("values", model.getattr("config")?.getattr("values")?), ("key", string(model.py(), key)), @@ -132,7 +132,7 @@ fn token<'py>(py: Python<'py>, bound: &Bound<'py, PyAny>) -> PyResult(py: Python<'py>, name: &str) -> PyResult> { - imported(py, CURSOR_TYPES, "BoundSentinel")?.getattr(name) + host::entry(py, ExecutorHost::BoundSentinel)?.getattr(name) } /// `bounds` clamped so a concrete window never reaches the exclusive configured `cursor_end`. @@ -172,7 +172,7 @@ fn timestamp_value<'py>(clock: &Bound<'py, PyAny>, scalar: &Scalar) -> PyResult< return Ok(clock.py().None().into_bound(clock.py())); }; call_with( - &imported(clock.py(), CURSOR_MODELS, "TimestampValue")?, + ®istry::class(clock.py(), RecordId::TimestampValue)?, &[("value", replaced(clock, ×tamp.local, timestamp.fold)?)], ) } @@ -186,7 +186,7 @@ fn rolling_window_bounds<'py>( let grain = config_str(model, "cursor_grain")?; let lookback = native( py, - "resolve_microbatch_lookback", + NativeAttr::ResolveMicrobatchLookback, &[("model", model.clone()), ("effective_grain", grain.clone())], )?; let lookback = if lookback.is_truthy()? { @@ -225,7 +225,7 @@ fn rolling_window_bounds<'py>( }; let (start, end) = session.checked(rolling_window(&clock, &span, grain))?; let mut bounds = call_with( - &imported(py, MODELS, "CursorBounds")?, + ®istry::class(py, RecordId::CursorBounds)?, &[ ("start", timestamp_value(&moment, &start)?), ("end", timestamp_value(&moment, &end)?), @@ -257,7 +257,7 @@ fn apply_future_safety<'py>( } native( py, - "resolve_future_cursor_safety", + NativeAttr::ResolveFutureCursorSafety, &[ ("bounds", bounds), ("cursor_type", typing.0.clone()), @@ -282,7 +282,7 @@ fn bounded_override<'py>( ) -> PyResult> { native( model.py(), - "resolve_bounded_cursor_override", + NativeAttr::ResolveBoundedCursorOverride, &[ ("start_cursor_override", inputs.overrides.0.clone()), ("end_cursor_override", inputs.overrides.1.clone()), @@ -314,7 +314,7 @@ fn snapshot_bounds<'py>( ) -> PyResult> { let py = model.py(); let policy = call_with( - &imported(py, MODELS, "MaximumStartPolicyInputs")?, + ®istry::class(py, RecordId::MaximumStartPolicyInputs)?, &[ ("config", inputs.policies.1.clone()), ("invocation_time", inputs.invocation_time.clone()), @@ -327,7 +327,7 @@ fn snapshot_bounds<'py>( )?; let bounds = native( py, - "compute_cursor_bounds", + NativeAttr::ComputeCursorBounds, &[ ("cursor_snapshot", request.cursor_snapshot.clone()), ("cursor_type", request.typing.0.clone()), @@ -362,7 +362,7 @@ fn model_cursor_snapshot<'py>( fn without_destination<'py>(cursor_snapshot: Bound<'py, PyAny>) -> PyResult> { native( cursor_snapshot.py(), - "without_destination_cursor", + NativeAttr::WithoutDestinationCursor, &[("cursor_snapshot", cursor_snapshot)], ) } @@ -451,7 +451,7 @@ pub(crate) fn microbatch_range<'py>( } let effective_grain = native( py, - "resolve_effective_timestamp_grain", + NativeAttr::ResolveEffectiveTimestampGrain, &[ ("cursor_type", cursor_type.clone()), ("downstream_grain", config_str(model, "cursor_grain")?), @@ -462,7 +462,7 @@ pub(crate) fn microbatch_range<'py>( if cursor_type.eq(TIMESTAMP)? && !effective_grain.is_none() { cursor_snapshot = native( py, - "normalize_cursor_snapshot_grain", + NativeAttr::NormalizeCursorSnapshotGrain, &[ ("cursor_snapshot", cursor_snapshot), ("cursor_type", cursor_type.clone()), @@ -472,7 +472,7 @@ pub(crate) fn microbatch_range<'py>( } let lookback = native( py, - "resolve_microbatch_lookback", + NativeAttr::ResolveMicrobatchLookback, &[ ("model", model.clone()), ("effective_grain", effective_grain.clone()), @@ -513,7 +513,7 @@ pub(crate) fn empty_cursor_inputs<'py>( } let decision = native( py, - "cursor_inputs_without_rows", + NativeAttr::CursorInputsWithoutRows, &[("cursor_snapshot", cursor_snapshot)], )?; Ok(( @@ -534,9 +534,7 @@ pub(crate) fn cursor_safety_configs<'py>( } fn compile_input_error(py: Python<'_>, message: String) -> PyErr { - match imported(py, COMPILE_EXCEPTIONS, "CompileInputError") - .and_then(|class| class.call1((message,))) - { + match registry::error(py, ErrorId::CompileInput).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_safety.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_safety.rs index 104c8965de..c1259754d4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_safety.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_safety.rs @@ -3,15 +3,14 @@ use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; use crate::bindings::_helpers::cursor::conversion::CursorSession; use crate::bindings::_helpers::planner_resolution::cursor_bounds::duration; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python}; use sqlbuild_cursor::algebra::models::{DurationSpan, Grain, Scalar}; use sqlbuild_cursor::planner::main::safety_horizon::safety_horizon; -pub(super) const MODELS: &str = "sqlbuild._native"; -pub(super) const EXCEPTIONS: &str = "sqlbuild._native"; - -pub(super) fn safety_error(py: Python<'_>, class: &str, message: &str) -> PyErr { - match imported(py, EXCEPTIONS, class).and_then(|class| class.call1((message,))) { +pub(super) fn safety_error(py: Python<'_>, class: ErrorId, message: &str) -> PyErr { + match registry::error(py, class).and_then(|class| class.call1((message,))) { Ok(value) => PyErr::from_value(value), Err(error) => error, } @@ -31,9 +30,9 @@ pub(super) fn safety_duration(value: &Bound<'_, PyAny>, future: bool) -> PyResul }); } let (class, field) = if future { - ("FutureCursorSafetyError", "future.max_distance") + (ErrorId::FutureCursorSafety, "future.max_distance") } else { - ("MaximumAutomaticStartError", "start.max_ahead") + (ErrorId::MaximumAutomaticStart, "start.max_ahead") }; Err(safety_error( value.py(), @@ -45,8 +44,8 @@ pub(super) fn safety_duration(value: &Bound<'_, PyAny>, future: bool) -> PyResul pub(super) fn timestamp_bound(value: &Bound<'_, PyAny>) -> PyResult { let py = value.py(); Ok( - value.is_instance(&imported(py, "sqlbuild._native", "DateValue")?)? - || value.is_instance(&imported(py, "sqlbuild._native", "TimestampValue")?)?, + value.is_instance(®istry::class(py, RecordId::DateValue)?)? + || value.is_instance(®istry::class(py, RecordId::TimestampValue)?)?, ) } @@ -63,7 +62,7 @@ pub(super) fn horizon<'py>( let py = session.py(); let (clock, grain) = clock_and_grain; let mut clock = utc_clock(clock)?; - let date_only = discovered.is_instance(&imported(py, "sqlbuild._native", "DateValue")?)?; + let date_only = discovered.is_instance(®istry::class(py, RecordId::DateValue)?)?; if date_only || discovered.getattr("value")?.getattr("tzinfo")?.is_none() { clock = call_with( &clock.getattr("replace")?, @@ -71,7 +70,7 @@ pub(super) fn horizon<'py>( )?; } let clock = call_with( - &imported(py, "sqlbuild._native", "TimestampValue")?, + ®istry::class(py, RecordId::TimestampValue)?, &[("value", clock)], )?; let grain = if grain.is_truthy()? { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_warnings.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_warnings.rs index 71d874c6bd..05fd7b0b17 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_warnings.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_warnings.rs @@ -1,8 +1,10 @@ //! Prominent warnings for applied future-end and automatic-start cursor safety. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::cursor::scalars::render_value; use crate::bindings::_helpers::planner_resolution::values::string; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult}; use pyo3::types::{PyString, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; @@ -78,12 +80,12 @@ pub(crate) fn with_cursor_safety_warnings<'py>( ] { let Some(message) = message else { continue }; all.push(call_with( - &imported(py, "sqlbuild._native", "PlanWarning")?, + ®istry::class(py, RecordId::PlanWarning)?, &[ ("model_name", model_name.clone()), ( "severity", - imported(py, "sqlbuild._native", "WarningSeverity")?.getattr("WARNING")?, + registry::class(py, RecordId::WarningSeverity)?.getattr("WARNING")?, ), ("code", string(py, code)), ("message", string(py, &message)), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_windows.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_windows.rs index 29be985ba0..096a31951d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_windows.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/cursor_windows.rs @@ -4,14 +4,14 @@ use crate::bindings::_helpers::compile_driver::python_calls::{call_with, importe use crate::bindings::_helpers::cursor::conversion::CursorSession; use crate::bindings::_helpers::cursor::scalars::parse_value; use crate::bindings::_helpers::planner_resolution::values::string; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; use pyo3::{IntoPyObject, pyfunction, wrap_pyfunction}; use sqlbuild_cursor::algebra::main::floor_to_grain::floor_to_grain; use sqlbuild_cursor::planner::main::empty_inputs::empty_inputs; -const MODELS: &str = "sqlbuild._native"; - #[pyfunction] #[pyo3(signature = (*, empty_input_names, waits_for_every_input, inputs_with_rows, has_window_start))] fn decide_empty_cursor_inputs<'py>( @@ -21,7 +21,7 @@ fn decide_empty_cursor_inputs<'py>( has_window_start: bool, ) -> PyResult> { let py = empty_input_names.py(); - let class = imported(py, MODELS, "EmptyCursorInputDecision")?; + let class = registry::class(py, RecordId::EmptyCursorInputDecision)?; if !empty_input_names.is_truthy()? || !empty_inputs(waits_for_every_input, inputs_with_rows, has_window_start) { @@ -48,7 +48,7 @@ fn cursor_inputs_without_rows<'py>( .getattr("unreadable_input_names")? .is_truthy()? { - return imported(cursor_snapshot.py(), MODELS, "EmptyCursorInputDecision")?.call0(); + return registry::class(cursor_snapshot.py(), RecordId::EmptyCursorInputDecision)?.call0(); } let rows = cursor_snapshot.getattr("upstream_maxes")?.is_truthy()? || cursor_snapshot @@ -94,12 +94,12 @@ fn empty_cursor_window<'py>( let mut bound = parse_value(&raw, &kind)?; if cursor_start.is_none() && !kind.eq("integer")? { bound = call_with( - &imported(py, "sqlbuild._native", "TimestampValue")?, + ®istry::class(py, RecordId::TimestampValue)?, &[("value", bound.getattr("value")?)], )?; } call_with( - &imported(py, MODELS, "CursorBounds")?, + ®istry::class(py, RecordId::CursorBounds)?, &[("start", bound.clone()), ("end", bound)], ) } @@ -143,7 +143,7 @@ fn unreadable_cursor_inputs_error<'py>( input_names: Vec, ) -> PyResult> { call_with( - &imported(py, "sqlbuild._native", "PlannerInputError")?, + ®istry::error(py, ErrorId::PlannerInput)?, &[ ( "message", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/future_safety.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/future_safety.rs index 099ce98ded..81ac8cc755 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/future_safety.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/future_safety.rs @@ -1,12 +1,12 @@ //! Future cursor safety evidence and exception boundary. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::cursor::conversion::CursorSession; use crate::bindings::_helpers::planner_resolution::cursor_safety::{ - MODELS, horizon, replace, safety_duration, safety_error, timestamp_bound, utc_clock, + horizon, replace, safety_duration, safety_error, timestamp_bound, utc_clock, }; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyTuple}; use pyo3::{IntoPyObject, pyfunction, wrap_pyfunction}; @@ -96,7 +96,7 @@ fn resolve_future_cursor_safety<'py>( if config.getattr("action")?.eq("error")? { return Err(safety_error( py, - "FutureCursorSafetyError", + ErrorId::FutureCursorSafety, &format!( "future cursor safety limit exceeded: effective bounds ['{}', '{}') exceed maximum allowed bounds ['{}', '{}') (cursors.future.max_distance={})", session.checked(render(&typed_start, &session))?, @@ -120,14 +120,14 @@ fn resolve_future_cursor_safety<'py>( } }; let safety = call_with( - &imported(py, MODELS, "FutureCursorSafetyEvidence")?, + ®istry::class(py, RecordId::FutureCursorSafetyEvidence)?, &[ ("action", config.getattr("action")?), ("max_distance", distance), ( "invocation_time", call_with( - &imported(py, "sqlbuild._native", "TimestampValue")?, + ®istry::class(py, RecordId::TimestampValue)?, &[("value", utc_clock(&clock)?)], )?, ), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/maximum_safety.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/maximum_safety.rs index 6d078b4098..39b8a0efe3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/maximum_safety.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/maximum_safety.rs @@ -1,16 +1,16 @@ //! Maximum automatic-start recovery and evidence boundary. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::cursor::conversion::CursorSession; use crate::bindings::_helpers::planner_resolution::cursor_bounds::{ optional_scalar, required_scalar, scalars, }; use crate::bindings::_helpers::planner_resolution::cursor_safety::{ - EXCEPTIONS, MODELS, horizon, replace, safety_duration, safety_error, timestamp_bound, utc_clock, + horizon, replace, safety_duration, safety_error, timestamp_bound, utc_clock, }; use crate::bindings::_helpers::planner_resolution::values::string; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{ Bound, PyAny, PyAnyMethods, PyErr, PyModule, PyModuleMethods, PyResult, Python, }; @@ -114,7 +114,7 @@ fn apply_maximum_start_policy<'py>( if !timestamp_bound(&physical)? { return Err(safety_error( py, - "MaximumAutomaticStartError", + ErrorId::MaximumAutomaticStart, "automatic start requires a timestamp cursor", )); } @@ -172,7 +172,7 @@ fn apply_maximum_start_policy<'py>( replay.push((key, args.value(key)?)); } call_with( - &imported(py, "sqlbuild._native", "apply_typed_cursor_replay_policy")?, + ®istry::native(py, NativeAttr::ApplyTypedCursorReplayPolicy)?, &replay, )? } else { @@ -181,14 +181,14 @@ fn apply_maximum_start_policy<'py>( let relation = snapshot.getattr("target_relation")?; let column = snapshot.getattr("destination_cursor_column")?; let evidence = call_with( - &imported(py, MODELS, "MaximumStartSafetyEvidence")?, + ®istry::class(py, RecordId::MaximumStartSafetyEvidence)?, &[ ("action", config.getattr("action")?), ("max_ahead", config.getattr("max_ahead")?), ( "invocation_time", call_with( - &imported(py, "sqlbuild._native", "TimestampValue")?, + ®istry::class(py, RecordId::TimestampValue)?, &[("value", utc_clock(&clock)?)], )?, ), @@ -219,7 +219,7 @@ fn apply_maximum_start_policy<'py>( )?; let error = |non_idempotent: bool| -> PyResult> { Err(PyErr::from_value(call_with( - &imported(py, EXCEPTIONS, "MaximumAutomaticStartError")?, + ®istry::error(py, ErrorId::MaximumAutomaticStart)?, &[ ( "message", @@ -248,7 +248,7 @@ fn apply_maximum_start_policy<'py>( fn rendered(value: &Bound<'_, PyAny>) -> PyResult { call_with( - &imported(value.py(), "sqlbuild._native", "cursor_render")?, + ®istry::native(value.py(), NativeAttr::CursorRender)?, &[("value", value.clone())], )? .extract() diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/orchestration.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/orchestration.rs index 9e0b106a42..3496d94b0c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/orchestration.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/orchestration.rs @@ -4,14 +4,13 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, }; use crate::bindings::_helpers::cursor::scalars::sentinel_to_token; -use crate::bindings::_helpers::planner_resolution::values::{NATIVE_MODULE, string}; +use crate::bindings::_helpers::planner_resolution::values::string; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyBool, PyDict, PyDictMethods, PyList, PyString, PyTuple}; use pyo3::{IntoPyObject, PyErr, pyfunction, wrap_pyfunction}; -const HOOK_MODULE: &str = "sqlbuild._native"; -const ERROR_MODULE: &str = "sqlbuild._native"; - struct Resolution<'py> { adapter: Bound<'py, PyAny>, context: Bound<'py, PyAny>, @@ -35,7 +34,7 @@ impl<'py> Resolution<'py> { } source.extend_from_slice(cursor); let mut resolved = call_with( - &imported(py, NATIVE_MODULE, "resolve_source_references")?, + ®istry::native(py, NativeAttr::ResolveSourceReferences)?, &source, )?; let mut refs = vec![("query_sql", resolved), ("adapter", self.adapter.clone())]; @@ -44,11 +43,11 @@ impl<'py> Resolution<'py> { } refs.extend_from_slice(cursor); resolved = call_with( - &imported(py, NATIVE_MODULE, "resolve_ref_references")?, + ®istry::native(py, NativeAttr::ResolveRefReferences)?, &refs, )?; resolved = call_with( - &imported(py, NATIVE_MODULE, "resolve_dbt_ref_references")?, + ®istry::native(py, NativeAttr::ResolveDbtRefReferences)?, &[ ("query_sql", resolved), ("external_sql_reference_resolver", self.external.clone()), @@ -61,11 +60,11 @@ impl<'py> Resolution<'py> { PyDict::new(py).into_any() }; for name in [ - "resolve_udf_references", - "resolve_table_function_references", + NativeAttr::ResolveUdfReferences, + NativeAttr::ResolveTableFunctionReferences, ] { resolved = call_with( - &imported(py, NATIVE_MODULE, name)?, + ®istry::native(py, name)?, &[ ("query_sql", resolved), ("function_locations", locations.clone()), @@ -78,11 +77,7 @@ impl<'py> Resolution<'py> { fn assert_resolved(&self, sql: &Bound<'py, PyAny>, label: &str) -> PyResult<()> { call_with( - &imported( - self.adapter.py(), - NATIVE_MODULE, - "assert_no_unresolved_sql_markers", - )?, + ®istry::native(self.adapter.py(), NativeAttr::AssertNoUnresolvedSqlMarkers)?, &[ ("sql", sql.clone()), ( @@ -114,7 +109,7 @@ impl<'py> Resolution<'py> { fn hook(&self, value: Bound<'py, PyAny>, label: &str) -> PyResult> { let py = value.py(); - if value.is_instance(&imported(py, HOOK_MODULE, "SqlHookEntry")?)? { + if value.is_instance(®istry::class(py, RecordId::SqlHookEntry)?)? { let kwargs = PyDict::new(py); kwargs.set_item( "statement", @@ -172,7 +167,7 @@ fn resolve_model_sql<'py>( external: args.value("external_sql_reference_resolver")?, }; let cursor_type = call_with( - &imported(py, NATIVE_MODULE, "get_config_str")?, + ®istry::native(py, NativeAttr::GetConfigStr)?, &[ ("values", model.getattr("config")?.getattr("values")?), ("key", string(py, "cursor_type")), @@ -180,7 +175,7 @@ fn resolve_model_sql<'py>( )?; let overrides = args.value("cursor_overrides")?; let bounds = call_with( - &imported(py, NATIVE_MODULE, "compute_model_cursor_bounds")?, + ®istry::native(py, NativeAttr::ComputeModelCursorBounds)?, &[ ("model", model.clone()), ("snapshot", args.value("snapshot")?), @@ -208,7 +203,7 @@ fn resolve_model_sql<'py>( ], )?; let roles = call_with( - &imported(py, NATIVE_MODULE, "resolve_cursor_input_roles")?, + ®istry::native(py, NativeAttr::ResolveCursorInputRoles)?, &[("model", model.clone())], )?; let inclusive = append_cursor_inclusive(&model)?; @@ -222,7 +217,7 @@ fn resolve_model_sql<'py>( ], )?; let intrinsics = call_with( - &imported(py, NATIVE_MODULE, "resolve_cursor_intrinsics")?, + ®istry::native(py, NativeAttr::ResolveCursorIntrinsics)?, &[("sql", sql.clone())], )?; let name: String = model.getattr("name")?.extract()?; @@ -248,7 +243,7 @@ fn resolve_model_sql<'py>( fn append_cursor_inclusive<'py>(model: &Bound<'py, PyAny>) -> PyResult> { let py = model.py(); let strategy = call_with( - &imported(py, NATIVE_MODULE, "get_config_str")?, + ®istry::native(py, NativeAttr::GetConfigStr)?, &[ ("values", model.getattr("config")?.getattr("values")?), ("key", string(py, "incremental_strategy")), @@ -286,7 +281,7 @@ fn intrinsic_bounds<'py>( let start_sql = literal("start")?; let end_sql = literal("end")?; call_with( - &imported(py, NATIVE_MODULE, "resolve_cursor_intrinsics")?, + ®istry::native(py, NativeAttr::ResolveCursorIntrinsics)?, &[ ("sql", sql.clone()), ("start_sql", start_sql), @@ -297,7 +292,7 @@ fn intrinsic_bounds<'py>( } fn unresolved_bounds_error(py: Python<'_>, name: &str, cause: Option<&str>) -> PyResult { - let error = imported(py, ERROR_MODULE, "PlannerInputError")?; + let error = registry::error(py, ErrorId::PlannerInput)?; let kwargs = PyDict::new(py); let message = if let Some(cause) = cause { kwargs.set_item("help", "rebuild it with an explicit cursor interval (--start-cursor-ts and --end-cursor-ts, or the -int forms), or make it a microbatch model so a full rebuild can run batch by batch")?; @@ -347,10 +342,7 @@ fn resolve_function_sql<'py>( } let resolution = Resolution { adapter: args.value("adapter")?, - context: call_with( - &imported(py, "sqlbuild._native", "ModelPlanContext")?, - &context, - )?, + context: call_with(®istry::class(py, RecordId::ModelPlanContext)?, &context)?, external: py.None().into_bound(py), }; let function = args.value("function")?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/refs.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/refs.rs index 24d868c4ff..e212bbf96f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/refs.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/refs.rs @@ -1,20 +1,17 @@ //! Reference lookup and adapter callbacks for planner SQL. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; -use crate::bindings::_helpers::planner_resolution::values::{ - NATIVE_MODULE, cursor_wrap, method, string, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::planner_resolution::values::{cursor_wrap, method, string}; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use pyo3::IntoPyObject; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods}; use pyo3::{pyfunction, wrap_pyfunction}; use sqlbuild_sqltext::resolution::main::{ref_alias::ref_alias, reference_spans::reference_spans}; -const RELATION_MODULE: &str = - "sqlbuild.adapter.relations.main.resolve_relation_location_qualified_name"; - #[pyfunction] #[pyo3(signature = (**kwargs))] fn resolve_ref_references(py: Python<'_>, kwargs: Option>) -> PyResult { @@ -49,11 +46,7 @@ fn resolve_ref_references(py: Python<'_>, kwargs: Option>) -> output.push_str(&sql[span.start..span.end]); } else { let relation: String = call_with( - &imported( - py, - RELATION_MODULE, - "resolve_relation_location_qualified_name", - )?, + &host::entry(py, AdapterHost::ResolveRelationLocationQualifiedName)?, &[("adapter", args.value("adapter")?), ("location", target)], )? .extract()?; @@ -124,7 +117,7 @@ fn resolve_function_calls( if spans.is_empty() { return Ok(query_sql.to_owned()); } - let scanner_type = imported(py, NATIVE_MODULE, "SqlCodeScanner")?; + let scanner_type = registry::native(py, NativeAttr::SqlCodeScanner)?; let scan = scanner_type.call1((query_sql, py.None(), "SQL reference resolution"))?; let mut output = String::new(); let mut last = 0; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/sources.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/sources.rs index 4898e1db42..8c09e4e2b5 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/sources.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/sources.rs @@ -2,7 +2,8 @@ use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; use crate::bindings::_helpers::planner_resolution::values::{cursor_wrap, method, string}; -use crate::bindings::_helpers::planner_strategy::values::{PLANNER_EXCEPTIONS_MODULE, input_error}; +use crate::bindings::_helpers::planner_strategy::values::input_error; +use crate::bindings::_helpers::records::ids::ErrorId; use crate::bindings::_helpers::references::source_relations::render_source_relation; use crate::bindings::_helpers::type_system::equality::types_equal; use pyo3::IntoPyObject; @@ -16,12 +17,7 @@ use sqlbuild_sqltext::resolution::main::{ }; fn source_error(py: Python<'_>, message: &str, code: &str) -> PyErr { - input_error( - py, - (PLANNER_EXCEPTIONS_MODULE, "PlannerInputError"), - message, - Some(code), - ) + input_error(py, ErrorId::PlannerInput, message, Some(code)) } fn rows(value: &Bound<'_, PyAny>) -> PyResult)>> { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/values.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/values.rs index 47d885cc62..489e2b8256 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/values.rs @@ -1,15 +1,13 @@ //! Boundary calls shared by reference and source resolution. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::PyString; use sqlbuild_sqltext::resolution::main::cursor_subquery::cursor_subquery; use sqlbuild_sqltext::resolution::models::CursorFilter; -pub(super) const NATIVE_MODULE: &str = "sqlbuild._native"; - pub(super) fn string<'py>(py: Python<'py>, text: &str) -> Bound<'py, PyAny> { PyString::new(py, text).into_any() } @@ -44,7 +42,7 @@ pub(super) fn cursor_wrap( let cursor_type = arguments.value("cursor_type")?; let literal = |bound: &str| -> PyResult { let token = call_with( - &imported(py, NATIVE_MODULE, "cursor_sentinel_to_token")?, + ®istry::native(py, NativeAttr::CursorSentinelToToken)?, &[("sentinel", bounds.getattr(bound)?)], )?; method( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/artifacts.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/artifacts.rs index edda0220c6..e0c5bbdedb 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/artifacts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/artifacts.rs @@ -1,13 +1,13 @@ //! Scenario artifact dataclasses and adapter identifier callbacks. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::planner_scenario::constants::{ SCENARIO_PLAN_HASH_COLLISION, SCENARIO_PLAN_INVALID_HASH_PREFIX, SCENARIO_PLAN_RELATION_COLLISION, }; -use crate::bindings::_helpers::planner_scenario::values::{MODELS, TYPES, error, string}; +use crate::bindings::_helpers::planner_scenario::values::{error, string}; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::relation_naming::fitting::fitted_logical_name; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python, pyfunction}; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; @@ -151,7 +151,7 @@ pub(crate) fn build_scenario_artifact_name( identifier_limit: i64, ) -> PyResult { let py = kind.py(); - let kind: String = imported(py, TYPES, "ScenarioArtifactKind")? + let kind: String = registry::class(py, RecordId::ScenarioArtifactKind)? .call1((kind,))? .getattr("value")? .extract()?; @@ -167,7 +167,7 @@ pub(crate) fn parse_scenario_artifact_physical_name<'py>( return Ok(py.None().into_bound(py)); }; call_with( - &imported(py, "sqlbuild._native", "ParsedScenarioArtifactName")?, + ®istry::class(py, RecordId::ParsedScenarioArtifactName)?, &[ ("hash_prefix", string(py, hash)), ("kind", string(py, kind)), @@ -221,7 +221,7 @@ pub(crate) fn build_scenario_relation_map<'py>( normalize.call1((&physical,))? }; if let Some(existing) = seen.get_item(&normalized)? { - let kind = imported(py, TYPES, "ScenarioArtifactKind")?; + let kind = registry::class(py, RecordId::ScenarioArtifactKind)?; let old_kind: String = kind .call1((existing.getattr("kind")?,))? .getattr("value")? @@ -243,7 +243,7 @@ pub(crate) fn build_scenario_relation_map<'py>( } seen.set_item(normalized, &artifact)?; artifacts.push(call_with( - &imported(py, "sqlbuild._native", "ScenarioArtifactName")?, + ®istry::class(py, RecordId::ScenarioArtifactName)?, &[ ("identity", artifact), ("physical_name", string(py, &physical)), @@ -251,7 +251,7 @@ pub(crate) fn build_scenario_relation_map<'py>( )?); } call_with( - &imported(py, MODELS, "ScenarioRelationMap")?, + ®istry::class(py, RecordId::ScenarioRelationMap)?, &[ ("scenario_name", args.value("scenario_name")?), ("hash_prefix", args.value("hash_prefix")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/cli.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/cli.rs index 9a24f89aa1..86669773d4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/cli.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/cli.rs @@ -1,17 +1,15 @@ //! Assemble CLI scenario plans while retaining adapter and executor boundaries. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::planner_scenario::constants::SCENARIO_PLAN_GRAPH_VALIDATION; use crate::bindings::_helpers::planner_scenario::graph::plan_scenario_graph; use crate::bindings::_helpers::planner_scenario::relations::build_scenario_relation_plan; -use crate::bindings::_helpers::planner_scenario::values::{TYPES, string}; +use crate::bindings::_helpers::planner_scenario::values::string; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python, pyfunction}; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; -const ARTIFACTS: &str = "sqlbuild._native"; - #[pyfunction] #[pyo3(signature = (**kwargs))] pub(crate) fn build_cli_scenario_plan<'py>( @@ -39,7 +37,7 @@ pub(crate) fn build_cli_scenario_plan<'py>( let (graph, warnings) = plan_scenario_graph(&scenario, &project, true, None)?; raise_for_warnings(warnings.as_any())?; let hashes = call_with( - &imported(py, ARTIFACTS, "build_scenario_hash_index")?, + ®istry::native(py, NativeAttr::BuildScenarioHashIndex)?, &[ ("project_name", args.value("project_name")?), ("scenarios", project.getattr("sql_scenarios")?), @@ -59,11 +57,11 @@ pub(crate) fn build_cli_scenario_plan<'py>( ] { for name in graph.getattr(field)?.try_iter()? { identities.push(call_with( - &imported(py, "sqlbuild._native", "ScenarioArtifactIdentity")?, + ®istry::class(py, RecordId::ScenarioArtifactIdentity)?, &[ ( "kind", - imported(py, TYPES, "ScenarioArtifactKind")?.getattr(kind)?, + registry::class(py, RecordId::ScenarioArtifactKind)?.getattr(kind)?, ), ("logical_name", name?), ], @@ -71,7 +69,7 @@ pub(crate) fn build_cli_scenario_plan<'py>( } } let map = call_with( - &imported(py, ARTIFACTS, "build_scenario_relation_map")?, + ®istry::native(py, NativeAttr::BuildScenarioRelationMap)?, &[ ("scenario_name", scenario.getattr("name")?), ("hash_prefix", hashes.get_item(scenario.getattr("name")?)?), @@ -99,7 +97,7 @@ pub(crate) fn build_cli_scenario_plan<'py>( } let relations = build_scenario_relation_plan(py, Some(relation_args))?; let result = call_with( - &imported(py, "sqlbuild._native", "build_scenario_execution_plan")?, + ®istry::native(py, NativeAttr::BuildScenarioExecutionPlan)?, &[ ("scenario", scenario), ("project", project), @@ -142,7 +140,7 @@ fn namespace<'py>( fn raise_for_warnings(warnings: &Bound<'_, PyAny>) -> PyResult<()> { let py = warnings.py(); - let severity = imported(py, "sqlbuild._native", "WarningSeverity")?.getattr("ERROR")?; + let severity = registry::class(py, RecordId::WarningSeverity)?.getattr("ERROR")?; let mut messages: Vec = Vec::new(); let mut code = None; for warning in warnings.try_iter()? { @@ -161,7 +159,7 @@ fn raise_for_warnings(warnings: &Bound<'_, PyAny>) -> PyResult<()> { } if let Some(code) = code { return Err(PyErr::from_value(call_with( - &imported(py, "sqlbuild._native", "PlannerInputError")?, + ®istry::error(py, ErrorId::PlannerInput)?, &[ ("message", string(py, &messages.join("\n"))), ("code", code), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/entries.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/entries.rs index 59729c0370..ac6fa6bdaf 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/entries.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/entries.rs @@ -1,15 +1,13 @@ //! Existing seed and function plan dataclass construction. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::compiled_project::loop_classes::by_attribute; use crate::bindings::_helpers::planner_scenario::constants::SCENARIO_PLAN_UNKNOWN_SEED; use crate::bindings::_helpers::planner_scenario::{ - models::SqlContext, - sql::required_target, - values::{MODELS, error}, + models::SqlContext, sql::required_target, values::error, }; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python, pyfunction}; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; @@ -44,7 +42,7 @@ pub(super) fn seed_entries<'py>( "seed", )?; result.push(call_with( - &imported(py, MODELS, "SeedPlanEntry")?, + ®istry::class(py, RecordId::SeedPlanEntry)?, &[ ("key", seed.getattr("key")?), ("name", seed.getattr("name")?), @@ -55,7 +53,7 @@ pub(super) fn seed_entries<'py>( ), ( "columns", - imported(py, "sqlbuild._native", "extract_seed_columns")?.call1((&seed,))?, + registry::native(py, NativeAttr::ExtractSeedColumns)?.call1((&seed,))?, ), ( "csv_settings", @@ -75,7 +73,7 @@ pub(super) fn function_entry<'py>( ) -> PyResult> { let py = function.py(); let body = call_with( - &imported(py, "sqlbuild._native", "resolve_function_sql")?, + ®istry::native(py, NativeAttr::ResolveFunctionSql)?, &[ ("adapter", context.adapter.clone()), ("function", function.clone()), @@ -110,12 +108,8 @@ pub(super) fn function_entry<'py>( fields.push(("body_sql", body)); fields.push(( "fingerprint_query_sql", - imported( - py, - "sqlbuild._native", - "build_compiled_function_fingerprint_sql", - )? - .call1((function,))?, + registry::native(py, NativeAttr::BuildCompiledFunctionFingerprintSql)? + .call1((function,))?, )); for name in [ "fingerprint_destination", @@ -128,7 +122,7 @@ pub(super) fn function_entry<'py>( ] { fields.push((name, function.getattr(name)?)); } - call_with(&imported(py, MODELS, "FunctionPlanEntry")?, &fields) + call_with(®istry::class(py, RecordId::FunctionPlanEntry)?, &fields) } #[pyfunction] diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/execution.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/execution.rs index f87b466239..ba484b2854 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/execution.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/execution.rs @@ -10,8 +10,10 @@ use crate::bindings::_helpers::planner_scenario::{ fixtures::{extract_fixtures, fixture_plans}, models::SqlContext, sql::{required_target, resolve_model_dbt_refs, resolve_sql, source_label}, - values::{MODELS, error, string}, + values::{error, string}, }; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python, pyfunction}; use pyo3::types::{PyBool, PyDict, PyDictMethods, PyTuple}; @@ -54,7 +56,7 @@ pub(crate) fn build_scenario_execution_plan<'py>( let snapshot = if snapshot.is_truthy()? { snapshot } else { - imported(py, "sqlbuild._native", "WarehouseSnapshot")?.call0()? + registry::class(py, RecordId::WarehouseSnapshot)?.call0()? }; let columns = args.value("source_warehouse_columns")?; let columns = if columns.is_truthy()? { @@ -70,12 +72,12 @@ pub(crate) fn build_scenario_execution_plan<'py>( }; let models = by_attribute(&project.getattr("models")?, "name")?; let functions = by_attribute(&project.getattr("functions")?, "key")?; - let locations = imported(py, "sqlbuild._native", "build_function_locations")? + let locations = registry::native(py, NativeAttr::BuildFunctionLocations)? .call1((project.getattr("functions")?,))?; let upstream = - imported(py, "sqlbuild._native", "build_execution_upstream_deps")?.call1((&project,))?; + registry::native(py, NativeAttr::BuildExecutionUpstreamDeps)?.call1((&project,))?; let ordered = call_with( - &imported(py, "sqlbuild._native", "topologically_order_keys")?, + ®istry::native(py, NativeAttr::TopologicallyOrderKeys)?, &[("upstream", upstream)], )?; let mut model_keys: Vec> = Vec::new(); @@ -121,14 +123,14 @@ pub(crate) fn build_scenario_execution_plan<'py>( &model.getattr("query_sql")?.extract::()?, &context.relations, )?; - let record = imported(py, "sqlbuild._native", "compiled_model_record")?.call1((&model,))?; + let record = registry::native(py, NativeAttr::CompiledModelRecord)?.call1((&model,))?; let changes = PyDict::new(py); changes.set_item("destination", destination)?; changes.set_item("query_sql", query)?; let model = imported(py, "dataclasses", "replace")?.call((record,), Some(&changes))?; let model_context = model_context(&project, &context, (&models, &locations, &columns))?; let result = call_with( - &imported(py, "sqlbuild._native", "plan_model")?, + ®istry::native(py, NativeAttr::PlanModel)?, &[ ("model", model), ("snapshot", snapshot.clone()), @@ -145,7 +147,7 @@ pub(crate) fn build_scenario_execution_plan<'py>( ("full_refresh", PyBool::new(py, true).to_owned().into_any()), ( "cursor_overrides", - imported(py, "sqlbuild._native", "CursorOverridePair")?.call0()?, + registry::class(py, RecordId::CursorOverridePair)?.call0()?, ), ], )?; @@ -156,7 +158,7 @@ pub(crate) fn build_scenario_execution_plan<'py>( } let (expected, assertions) = expectations(&scenario, &context)?; let plan = call_with( - &imported(py, MODELS, "ScenarioExecutionPlan")?, + ®istry::class(py, RecordId::ScenarioExecutionPlan)?, &[ ("key", scenario.getattr("key")?), ("name", scenario.getattr("name")?), @@ -206,7 +208,7 @@ fn complete_fixtures<'py>( .map(|key| key.getattr("name")) .collect::>>()?; let completion = call_with( - &imported(py, "sqlbuild._native", "build_relation_fixture_completion")?, + ®istry::native(py, NativeAttr::BuildRelationFixtureCompletion)?, &[ ("project", project.clone()), ("adapter", context.adapter.clone()), @@ -255,7 +257,7 @@ fn model_context<'py>( let (models, locations, columns) = inputs; let py = project.py(); call_with( - &imported(py, "sqlbuild._native", "ModelPlanContext")?, + ®istry::class(py, RecordId::ModelPlanContext)?, &[ ( "model_locations", @@ -298,7 +300,7 @@ fn expectations<'py>( false, )?; expected.push(call_with( - &imported(py, MODELS, "ScenarioExpectedExpectationPlan")?, + ®istry::class(py, RecordId::ScenarioExpectedExpectationPlan)?, &[ ("model_name", string(py, name)), ("actual_destination", target), @@ -316,7 +318,7 @@ fn expectations<'py>( false, )?; assertions.push(call_with( - &imported(py, MODELS, "ScenarioAssertionExpectationPlan")?, + ®istry::class(py, RecordId::ScenarioAssertionExpectationPlan)?, &[ ( "name", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/fixtures.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/fixtures.rs index 8d21bc9df3..3ed53c053d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/fixtures.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/fixtures.rs @@ -1,14 +1,14 @@ //! Materialize fixture SQL plans while preserving adapter and completion boundaries. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::planner_scenario::constants::SCENARIO_PLAN_MISSING_FIXTURE_SQL; use crate::bindings::_helpers::planner_scenario::{ models::SqlContext, sql::{required_target, resolve_sql, source_label}, - values::{MODELS, TYPES, error, string}, + values::{error, string}, }; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python, pyfunction}; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; @@ -90,11 +90,11 @@ pub(super) fn fixture_plans<'py>( format!("WITH {} {sql}", helpers.join(", ")) }; result.push(call_with( - &imported(py, MODELS, "ScenarioFixturePlan")?, + ®istry::class(py, RecordId::ScenarioFixturePlan)?, &[ ( "kind", - imported(py, TYPES, "ScenarioArtifactKind")?.call1((kind,))?, + registry::class(py, RecordId::ScenarioArtifactKind)?.call1((kind,))?, ), ("logical_name", string(py, &name)), ("destination", target), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/graph.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/graph.rs index d0ec52b3ce..4be6a925ee 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/graph.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/graph.rs @@ -1,9 +1,11 @@ //! Convert project views into pure scenario dependency facts. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::graph::keys::{KeyCache, native_key}; use crate::bindings::_helpers::planner_scenario::constants::SCENARIO_PLAN_GRAPH_VALIDATION; use crate::bindings::_helpers::planner_scenario::values::string; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, pyfunction}; use pyo3::types::PyTuple; use sqlbuild_analysis::planner_scenario::main::graph::graph; @@ -55,7 +57,7 @@ pub(crate) fn plan_scenario_graph<'py>( input.targets.sort(); input.assertions.sort(); let plan = call_with( - &imported(py, "sqlbuild._native", "ScenarioGraphPlan")?, + ®istry::class(py, RecordId::ScenarioGraphPlan)?, &[ ("key", scenario.getattr("key")?), ("name", scenario.getattr("name")?), @@ -97,7 +99,7 @@ pub(crate) fn plan_scenario_graph<'py>( ("model_name", py.None().into_bound(py)), ( "severity", - imported(py, "sqlbuild._native", "WarningSeverity")?.getattr("ERROR")?, + registry::class(py, RecordId::WarningSeverity)?.getattr("ERROR")?, ), ("message", string(py, &message)), ]; @@ -105,7 +107,7 @@ pub(crate) fn plan_scenario_graph<'py>( values.push(("code", string(py, SCENARIO_PLAN_GRAPH_VALIDATION))); } warnings.push(call_with( - &imported(py, "sqlbuild._native", "PlanWarning")?, + ®istry::class(py, RecordId::PlanWarning)?, &values, )?); } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/relations.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/relations.rs index cb8e14f3d1..07a8992cd9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/relations.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/relations.rs @@ -7,7 +7,9 @@ use crate::bindings::_helpers::planner_scenario::constants::{REF, SOURCE}; use crate::bindings::_helpers::planner_scenario::constants::{ SCENARIO_PLAN_INTERNAL, SCENARIO_PLAN_MISSING_RELATION_TARGET, }; -use crate::bindings::_helpers::planner_scenario::values::{MODELS, error}; +use crate::bindings::_helpers::planner_scenario::values::error; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python, pyfunction}; use pyo3::types::{PyDict, PyDictMethods}; @@ -100,7 +102,7 @@ pub(crate) fn build_scenario_relation_plan<'py>( seed_fixtures.set_item(name, target)?; } call_with( - &imported(py, MODELS, "ScenarioRelationPlan")?, + ®istry::class(py, RecordId::ScenarioRelationPlan)?, &[ ("scenario_name", graph.getattr("name")?), ("relation_map", args.value("relation_map")?), @@ -142,7 +144,7 @@ fn target_for_artifact<'py>( ], )?; call_with( - &imported(py, "sqlbuild._native", "CompiledRelationLocation")?, + ®istry::class(py, RecordId::CompiledRelationLocation)?, &[ ("database", database), ("schema", schema), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/sql.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/sql.rs index 5fceaafc9c..32528173d4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/sql.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/sql.rs @@ -1,8 +1,6 @@ //! Resolve scenario SQL through the shared native scanner and adapter callbacks. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::planner_scenario::constants::{ SCENARIO_PLAN_MISSING_RELATION_TARGET, SCENARIO_PLAN_UNRESOLVED_RELATION_MARKER, }; @@ -10,6 +8,8 @@ use crate::bindings::_helpers::planner_scenario::{ models::SqlContext, values::{error, string}, }; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sqltext::lexical_syntax::LexicalSyntaxInput; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python, pyfunction}; use pyo3::types::{PyDict, PyStringMethods}; @@ -46,7 +46,7 @@ pub(super) fn resolve_sql( result = replace_markers(&result, kind, Some(syntax.clone().into()), |name, marker| { let target = locations.call_method1("get", (name,))?; let target = if target.is_none() {target} else if kind == super::constants::SOURCE_MARKER { - call_with(&imported(py, "sqlbuild._native", "render_source_relation")?, &[("entry", target), ("adapter", context.adapter.clone())])? + call_with(®istry::native(py, NativeAttr::RenderSourceRelation)?, &[("entry", target), ("adapter", context.adapter.clone())])? } else {target.getattr("qualified_name")?}; if target.is_none() { let marker = string(py, marker).repr()?.to_str()?.to_owned(); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/values.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/values.rs index 57b3d25c93..ac7486a74d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/values.rs @@ -1,12 +1,11 @@ //! Existing Python model constructors and scenario diagnostics. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyErr, PyResult, Python}; use pyo3::types::PyString; -pub(super) const MODELS: &str = "sqlbuild._native"; -pub(super) const TYPES: &str = "sqlbuild._native"; - pub(super) fn string<'py>(py: Python<'py>, text: &str) -> Bound<'py, PyAny> { PyString::new(py, text).into_any() } @@ -22,7 +21,7 @@ pub(super) fn error( args.push(("help", string(py, help))); } Ok(PyErr::from_value(call_with( - &imported(py, "sqlbuild._native", "PlannerInputError")?, + ®istry::error(py, ErrorId::PlannerInput)?, &args, )?)) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/loaders.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/loaders.rs index 40cc222cce..22ad2c1584 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/loaders.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/loaders.rs @@ -1,11 +1,13 @@ //! Loader dependency mapping and synthetic source boundary construction. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::graph::keys::KeyCache; use crate::bindings::_helpers::graph::views::edge_dict; use crate::bindings::_helpers::planner_scope::values::{ - NATIVE, SOURCE_MODELS, dependency_names, edges, keys, loader_graph, loader_names, project_graph, + dependency_names, edges, keys, loader_graph, loader_names, project_graph, }; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, IntoPyObject, PyAny, PyAnyMethods, PyResult, pyfunction}; use pyo3::types::{PyDict, PyDictMethods, PyFrozenSet, PyTuple}; use sqlbuild_analysis::planner_scope::main::{ @@ -40,7 +42,7 @@ pub(crate) fn loader_to_source_entry<'py>( .into_any(); } let parts = call_with( - &imported(py, NATIVE, "loader_destination_parts")?, + ®istry::native(py, NativeAttr::LoaderDestinationPartsFunction)?, &[ ("destination", destination), ( @@ -71,7 +73,7 @@ pub(crate) fn loader_to_source_entry<'py>( let meta = PyDict::new(py); meta.set_item("sqlbuild_loader_node", true)?; kwargs.set_item("meta", meta)?; - imported(py, SOURCE_MODELS, "SourceEntry")?.call((), Some(&kwargs)) + registry::class(py, RecordId::SourceEntry)?.call((), Some(&kwargs)) } pub(crate) fn intermediate_map<'py>( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/scope.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/scope.rs index 0a194a1d52..6daf82827c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/scope.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/scope.rs @@ -1,12 +1,12 @@ //! Planner scope assembly using native graph and selector operations. -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::graph::keys::KeyCache; use crate::bindings::_helpers::planner_scope::loaders::expand_selected_loader_dependencies; -use crate::bindings::_helpers::planner_scope::values::{NATIVE, edges, keys}; +use crate::bindings::_helpers::planner_scope::values::{edges, keys}; use crate::bindings::_helpers::planner_selection::carrier::{Carrier, py_strings}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python, pyfunction}; use pyo3::types::PyDictMethods; use pyo3::types::{PyDict, PyFrozenSet, PyTuple}; @@ -16,8 +16,6 @@ use sqlbuild_analysis::planner_scope::main::{ }; use std::collections::HashSet; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - #[pyfunction] #[pyo3(signature = (*, selected_keys, upstream_deps, project))] pub(crate) fn managed_source_upstream_keys<'py>( @@ -65,14 +63,16 @@ pub(crate) fn build_planner_scope<'py>( let select = default_tuple(&args.value("select")?); let exclude = default_tuple(&args.value("exclude")?); let auto_load = args.value("auto_load_sources")?.is_truthy()?; - let mut upstream = imported(py, NATIVE, "build_execution_upstream_deps")?.call1((&project,))?; - let mut downstream = imported(py, NATIVE, "build_downstream_deps")?.call1((&upstream,))?; + let mut upstream = + registry::native(py, NativeAttr::BuildExecutionUpstreamDeps)?.call1((&project,))?; + let mut downstream = + registry::native(py, NativeAttr::BuildDownstreamDeps)?.call1((&upstream,))?; let lineage = project.getattr("lineage_graph")?; - let all_keys = imported(py, NATIVE, "lineage_graph_names")?.call1((&lineage,))?; + let all_keys = registry::native(py, NativeAttr::LineageGraphNames)?.call1((&lineage,))?; let mut selected = args.value("selected_keys")?; if selected.is_none() { selected = call_with( - &imported(py, NATIVE, "resolve_graph_selection")?, + ®istry::native(py, NativeAttr::ResolveGraphSelection)?, &[ ("graph", lineage), ("select", select.clone()), @@ -114,11 +114,12 @@ pub(crate) fn build_planner_scope<'py>( expand_selected_loader_dependencies(&project, &selected, &upstream, Some(&executable))?; selected = expanded.into_any(); upstream = deps.into_any(); - downstream = imported(py, NATIVE, "build_downstream_deps")?.call1((&upstream,))?; + downstream = registry::native(py, NativeAttr::BuildDownstreamDeps)?.call1((&upstream,))?; } - let origins = imported(py, NATIVE, "build_execution_edge_origins")?.call1((&project,))?; + let origins = + registry::native(py, NativeAttr::BuildExecutionEdgeOrigins)?.call1((&project,))?; let order = call_with( - &imported(py, NATIVE, "topologically_order_keys")?, + ®istry::native(py, NativeAttr::TopologicallyOrderKeys)?, &[ ("upstream", upstream.clone()), ("injected_edge_origins", origins), @@ -135,10 +136,10 @@ pub(crate) fn build_planner_scope<'py>( } let mut tests = args.value("sql_test_selection")?; if !tests.is_truthy()? { - tests = imported(py, NATIVE_RECORDS_MODULE, "SqlTestSelection")?.call0()?; + tests = registry::class(py, RecordId::SqlTestSelection)?.call0()?; } call_with( - &imported(py, NATIVE_RECORDS_MODULE, "PlannerScope")?, + ®istry::class(py, RecordId::PlannerScope)?, &[ ("upstream_deps", upstream), ("downstream_deps", downstream), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/sources.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/sources.rs index ba79f23caa..1eccb0a12c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/sources.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/sources.rs @@ -1,16 +1,16 @@ //! Existing source and load plan models materialised at the Python boundary. -use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::graph::algorithms::closure; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_scope::loaders::intermediate_map; -use crate::bindings::_helpers::planner_scope::values::MODELS; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, pyfunction}; use pyo3::types::{PyBool, PyDict, PyTuple}; use pyo3::types::{PyDictMethods, PySetMethods}; use sqlbuild_analysis::planner_scope::constants::SOURCE; -const EXECUTION_TYPES: &str = "sqlbuild.runtime.contracts.types"; - #[pyfunction] #[pyo3(signature = (*, project, selected_keys, upstream_deps = None))] pub(crate) fn build_source_load_map<'py>( @@ -55,8 +55,8 @@ pub(crate) fn build_source_load_entries<'py>( is_reload: bool, ) -> PyResult> { let py = execution_order.py(); - let class = imported(py, MODELS, "SourceLoadPlanEntry")?; - let kinds = imported(py, EXECUTION_TYPES, "ExecutionResourceKind")?; + let class = registry::class(py, RecordId::SourceLoadPlanEntry)?; + let kinds = host::entry(py, ExecutorHost::ExecutionResourceKind)?; let mut entries: Vec> = Vec::new(); for key in execution_order.try_iter()? { let key = key?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/values.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/values.rs index 56545f3785..1f791d8c01 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/values.rs @@ -7,10 +7,6 @@ use pyo3::types::PyDictMethods; use sqlbuild_analysis::assembly::project::types::ObjectKey; use sqlbuild_analysis::planner_scope::models::LoaderGraph; -pub(crate) const NATIVE: &str = "sqlbuild._native"; -pub(crate) const MODELS: &str = "sqlbuild._native"; -pub(crate) const SOURCE_MODELS: &str = "sqlbuild._native"; - pub(crate) fn keys(values: &Bound<'_, PyAny>) -> PyResult> { values .try_iter()? diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/carrier.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/carrier.rs index a34b4384f6..95dbc9e71d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/carrier.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/carrier.rs @@ -6,9 +6,9 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyString, PyStringMethods}; use sqlbuild_analysis::graph::errors::SelectorError; -use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; -const PLANNER_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; const PRIVATE_USE: std::ops::Range = 0xF0000..0xFFFFE; /// `str.translate` tables to and from carrier characters; empty when no text holds a surrogate. @@ -107,7 +107,7 @@ impl<'py> Carrier<'py> { .map(|help| self.back(help)) .transpose()?; keywords.set_item("help", help)?; - imported(self.py, PLANNER_EXCEPTIONS_MODULE, "PlannerInputError")? + registry::error(self.py, ErrorId::PlannerInput)? .call((self.back(&error.message)?,), Some(&keywords)) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/model_selectors.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/model_selectors.rs index eb4b6d65ac..69a6e63fc8 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/model_selectors.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/model_selectors.rs @@ -14,6 +14,8 @@ use sqlbuild_analysis::planner_selection::models::{ use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; use crate::bindings::_helpers::planner_selection::carrier::Carrier; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; const SHARED_EXCEPTIONS_MODULE: &str = "sqlbuild.errors.contracts.exceptions"; @@ -34,7 +36,7 @@ fn split_selector_expansion<'py>(raw: &Bound<'py, PyString>) -> PyResult( upstream, downstream, } => call_with( - &imported(py, NATIVE_RECORDS_MODULE, "ParsedSelector")?, + ®istry::class(py, RecordId::ParsedSelector)?, &[ ( "kind", - imported(py, NATIVE_RECORDS_MODULE, "SelectorKind")?.call1((kind,))?, + registry::class(py, RecordId::SelectorKind)?.call1((kind,))?, ), ("value", carrier.back(&value)?.into_any()), ("upstream", PyBool::new(py, upstream).to_owned().into_any()), @@ -66,7 +64,7 @@ pub(crate) fn parsed_selector<'py>( upstream, downstream, } => call_with( - &imported(py, NATIVE_RECORDS_MODULE, "PathSelector")?, + ®istry::class(py, RecordId::PathSelector)?, &[ ("start_name", carrier.back(&start)?.into_any()), ("end_name", carrier.back(&end)?.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/unit_tests.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/unit_tests.rs index 8f063da418..b420ee0868 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/unit_tests.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/unit_tests.rs @@ -10,10 +10,10 @@ use sqlbuild_analysis::planner_selection::models::{ SelectorGroups, UnitTestSelectorNames, UnitTestSelectorSplit, }; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::planner_selection::carrier::{Carrier, py_strings}; - -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; const RESOURCE_ENTRY_FILES: [(&str, &str); 3] = [ ("schema_files", "model_entries"), @@ -111,7 +111,7 @@ fn split_python<'py>( ) .map_err(|error| carrier.error(error))?; let selection: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "SqlTestSelection")?, + ®istry::class(py, RecordId::SqlTestSelection)?, &[ ("names", PyFrozenSet::new(py, &split.test_names)?.into_any()), ( @@ -129,7 +129,7 @@ fn split_python<'py>( ], )?; call_with( - &imported(py, "sqlbuild._native", "UnitTestSelectorSplit")?, + ®istry::class(py, RecordId::UnitTestSelectorSplit)?, &[ ("select", restored(&carrier, &split.select)?.into_any()), ("exclude", restored(&carrier, &split.exclude)?.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/audit_entries.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/audit_entries.rs index 5b1a3d6b84..793f79e5e3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/audit_entries.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/audit_entries.rs @@ -10,9 +10,10 @@ use crate::bindings::_helpers::boundary::keyword_request::Request; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; use crate::bindings::_helpers::planner_strategy::constants::{ - AUDIT_TYPES_MODULE, COMPILE_MODELS_MODULE, COMPILE_TYPES_MODULE, MODEL_ATTACHMENT, - NATIVE_MODULE, PLAN_MODELS_MODULE, SOURCE_ATTACHMENT, + AUDIT_TYPES_MODULE, COMPILE_TYPES_MODULE, MODEL_ATTACHMENT, SOURCE_ATTACHMENT, }; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; /// Render and validate one audit query with the supplied adapter callbacks. fn render_query<'py>( @@ -22,7 +23,7 @@ fn render_query<'py>( ) -> PyResult> { let py = request.py(); let resolved = call_with( - &imported(py, NATIVE_MODULE, "render_audit_sql")?, + ®istry::native(py, NativeAttr::RenderAuditSql)?, &[ ("unresolved_sql", sql), ("model_locations", request.get("model_locations")?), @@ -32,7 +33,7 @@ fn render_query<'py>( ], )?; call_with( - &imported(py, NATIVE_MODULE, "assert_no_unresolved_sql_markers")?, + ®istry::native(py, NativeAttr::AssertNoUnresolvedSqlMarkers)?, &[ ("sql", resolved.clone()), ("context", PyString::new(py, context).into_any()), @@ -81,7 +82,7 @@ pub(crate) fn plan_audit<'py>(request: Option<&Bound<'py, PyDict>>) -> PyResult< )? }; let attachment = call_with( - &imported(py, NATIVE_MODULE, "resolve_attachment_kind")?, + ®istry::native(py, NativeAttr::ResolveAttachmentKind)?, &[ ("audit", audit.clone()), ("downstream_deps", request.get("lineage_downstream_deps")?), @@ -101,7 +102,7 @@ pub(crate) fn plan_audit<'py>(request: Option<&Bound<'py, PyDict>>) -> PyResult< let requested = audit_enum(&audit, "run_scope", "AuditRunScope", "final")?; let severity = audit_enum(&audit, "severity", "AuditSeverity", "warn")?; let effective = call_with( - &imported(py, NATIVE_MODULE, "resolve_effective_run_scope")?, + ®istry::native(py, NativeAttr::ResolveEffectiveRunScope)?, &[ ("requested_run_scope", requested.clone()), ("attached_model_materialization", materialization), @@ -153,7 +154,7 @@ pub(crate) fn plan_audit<'py>(request: Option<&Bound<'py, PyDict>>) -> PyResult< ( "reads_outside_target_lineage", call_with( - &imported(py, NATIVE_MODULE, "reads_outside_target_lineage")?, + ®istry::native(py, NativeAttr::ReadsOutsideTargetLineage)?, &[ ("audit", audit), ("upstream_deps", request.get("lineage_upstream_deps")?), @@ -163,7 +164,7 @@ pub(crate) fn plan_audit<'py>(request: Option<&Bound<'py, PyDict>>) -> PyResult< ] { values.set_item(field, value)?; } - imported(py, PLAN_MODELS_MODULE, "AuditPlanEntry")?.call((), Some(&values)) + registry::class(py, RecordId::AuditPlanEntry)?.call((), Some(&values)) } /// Select attached audits by their logical target and singular audits by dependency overlap. @@ -183,7 +184,7 @@ pub(crate) fn audit_is_selected( let resource_type = imported(py, COMPILE_TYPES_MODULE, "CompiledResourceType")?.call1((target_value,))?; let key = call_with( - &imported(py, COMPILE_MODELS_MODULE, "CompiledObjectKey")?, + ®istry::class(py, RecordId::CompiledObjectKey)?, &[("resource_type", resource_type), ("name", name)], )?; return selected_keys.contains(key); @@ -217,7 +218,7 @@ pub(crate) fn build_selected_audit_entries<'py>( if selected.is_empty() { return Ok(entries); } - let lineage = imported(py, NATIVE_MODULE, "project_lineage_views")?.call1((project,))?; + let lineage = registry::native(py, NativeAttr::ProjectLineageViews)?.call1((project,))?; let relations = request.get("relations")?; let arguments = PyDict::new(py); for field in ["model_locations", "seed_locations"] { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/audits.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/audits.rs index b0e46157c8..ba45519683 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/audits.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/audits.rs @@ -11,12 +11,11 @@ use sqlbuild_analysis::planner_strategy::models::{AuditAttachmentKind, AuditRunS use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; use crate::bindings::_helpers::graph::algorithms::closure; -use crate::bindings::_helpers::planner_strategy::values::{ - PLANNER_EXCEPTIONS_MODULE, input_error, optional_string, text, -}; +use crate::bindings::_helpers::planner_strategy::values::{input_error, optional_string, text}; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; const AUDITING_TYPES_MODULE: &str = "sqlbuild.compiler.auditing.types"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; const MODEL_RESOURCE_TYPE: &str = "model"; const FINAL_RUN_SCOPE: &str = "final"; @@ -24,7 +23,7 @@ const FINAL_RUN_SCOPE: &str = "final"; /// `CompiledObjectKey(resource_type=CompiledResourceType(kind), name=name)`. fn object_key<'py>(py: Python<'py>, kind: &str, name: &str) -> PyResult> { call_with( - &imported(py, COMPILE_MODELS_MODULE, "CompiledObjectKey")?, + ®istry::class(py, RecordId::CompiledObjectKey)?, &[ ( "resource_type", @@ -85,7 +84,7 @@ fn resolve_attachment_kind<'py>( if name.is_none() { return Err(input_error( py, - (PLANNER_EXCEPTIONS_MODULE, "PlannerInputError"), + ErrorId::PlannerInput, &format!( "audit '{}': attached audit is missing an attached target name", text(&audit.getattr("name")?)? diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/constants.rs index b306b03409..4646fba279 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/constants.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/constants.rs @@ -1,15 +1,9 @@ //! Audit plan boundary modules and attachment values. -pub(crate) const NATIVE_MODULE: &str = "sqlbuild._native"; pub(crate) const AUDIT_TYPES_MODULE: &str = "sqlbuild.compiler.auditing.types"; pub(crate) const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; -pub(crate) const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; -pub(crate) const PLAN_MODELS_MODULE: &str = "sqlbuild._native"; pub(crate) const MODEL_ATTACHMENT: &str = "model"; pub(crate) const SOURCE_ATTACHMENT: &str = "source"; -pub(crate) const PLANNER_TYPES_MODULE: &str = "sqlbuild._native"; -pub(crate) const ADAPTER_MODELS_MODULE: &str = "sqlbuild.adapter.contract.models"; -pub(crate) const CURSOR_TYPES_MODULE: &str = "sqlbuild.cursor_algebra.types"; pub(crate) const INCREMENTAL_MATERIALIZATION: &str = "incremental"; pub(crate) const MICROBATCH_MODE: &str = "microbatch"; pub(crate) const CUSTOM_MATERIALIZATION: &str = "custom"; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/cursor_inputs.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/cursor_inputs.rs index d877d50c3f..40cc22382d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/cursor_inputs.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/cursor_inputs.rs @@ -9,18 +9,18 @@ use sqlbuild_analysis::planner_strategy::models::{CursorInputColumnCheck, Cursor use crate::bindings::_helpers::boundary::keyword_request::Request; use crate::bindings::_helpers::compile_attachment::cursor_roles::resolve_cursor_input_roles; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::cursor::scalars::parse_value; use crate::bindings::_helpers::model_config::config_values::{config_cursor_bound, config_str}; use crate::bindings::_helpers::planner_identity::metadata::or_empty; use crate::bindings::_helpers::planner_strategy::constants::{ - ENFORCED_CONTRACT, INCREMENTAL_MATERIALIZATION, MODEL_REFERENCE, PLAN_MODELS_MODULE, - SEED_REFERENCE, SOURCE_REFERENCE, TIMESTAMP_CURSOR, + ENFORCED_CONTRACT, INCREMENTAL_MATERIALIZATION, MODEL_REFERENCE, SEED_REFERENCE, + SOURCE_REFERENCE, TIMESTAMP_CURSOR, }; use crate::bindings::_helpers::planner_strategy::lineage::resolve_lineage_reference; -use crate::bindings::_helpers::planner_strategy::values::{ - PLANNER_EXCEPTIONS_MODULE, input_error, text, -}; +use crate::bindings::_helpers::planner_strategy::values::{input_error, text}; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::references::source_relations::render_source_relation; /// A model's authored config values. @@ -52,12 +52,7 @@ fn names(items: &Bound<'_, PyAny>) -> PyResult> { /// Raise the planner error of a cursor input column its input does not expose. fn check_column(check: CursorInputColumnCheck, py: Python<'_>) -> PyResult<()> { match cursor_input_column_failure(&check) { - Some((message, code)) => Err(input_error( - py, - (PLANNER_EXCEPTIONS_MODULE, "PlannerInputError"), - &message, - Some(code), - )), + Some((message, code)) => Err(input_error(py, ErrorId::PlannerInput, &message, Some(code))), None => Ok(()), } } @@ -188,7 +183,7 @@ fn input_kind<'py>( SOURCE_REFERENCE => "source", _ => return Ok(py.None().into_bound(py)), }; - imported(py, "sqlbuild._native", "GraphResourceKind")?.call1((value,)) + registry::class(py, RecordId::GraphResourceKind)?.call1((value,)) } /// A producer model's terminal cursor bound parsed with the consumer's cursor type. @@ -226,7 +221,7 @@ pub(crate) fn build_cursor_input_relations<'py>( Some(cursor_type) if cursor_type.is_truthy()? => cursor_type, _ => PyString::new(py, TIMESTAMP_CURSOR).into_any(), }; - let class: Bound<'py, PyAny> = imported(py, PLAN_MODELS_MODULE, "CursorInputRelation")?; + let class: Bound<'py, PyAny> = registry::class(py, RecordId::CursorInputRelation)?; let mut relations: Vec> = Vec::new(); let watermarks: Bound<'py, PyAny> = resolve_cursor_input_roles(&model)?.getattr("watermark_inputs")?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/cursor_types.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/cursor_types.rs index 848cb748f2..b20c8c11e1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/cursor_types.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/cursor_types.rs @@ -12,17 +12,19 @@ use sqlbuild_analysis::planner_strategy::models::{CursorType, CursorTypeCheck}; use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, }; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_changes::results::planner_enum; use crate::bindings::_helpers::planner_strategy::values::{optional_string, plan_warning, text}; +use crate::bindings::_helpers::records::ids::RecordId; -const DEBUG_EVENT_MODULE: &str = "sqlbuild.diagnostics.main.log_debug_event"; const PLANNER_LOGGER: &str = "sqlbuild.planner"; /// Log the Polyglot parse error that left a warehouse type unclassified. fn log_parse_failure(py: Python<'_>, failure: &TypeParseFailure) -> PyResult<()> { let text = |value: &str| PyString::new(py, value).into_any(); call_with( - &imported(py, DEBUG_EVENT_MODULE, "log_debug_event")?, + &host::entry(py, ExecutorHost::LogDebugEvent)?, &[ ( "logger", @@ -59,7 +61,7 @@ fn classify_cursor_sql_type<'py>( CursorType::Timestamp => "timestamp", CursorType::Integer => "integer", }; - planner_enum(py, "CursorType", value) + planner_enum(py, RecordId::CursorType, value) }) .transpose() } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_completion.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_completion.rs index 6833326347..f614f02137 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_completion.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_completion.rs @@ -17,6 +17,7 @@ use crate::bindings::_helpers::planner_strategy::fixture_types::expected_fixture use crate::bindings::_helpers::planner_strategy::fixture_values::{ columns, diagnostic, folded, label, record, strings, }; +use crate::bindings::_helpers::records::ids::RecordId; #[pyfunction] #[pyo3(signature = (*, project, adapter, request, planning_context = None))] @@ -198,7 +199,7 @@ fn complete_prepared<'py>( kwargs.set_item("inferred_by_fixture", prepared.inferred)?; kwargs.set_item("expected_types", expected)?; kwargs.set_item("diagnostics", PyTuple::new(py, prepared.diagnostics)?)?; - record(py, "RelationFixtureCompletion", &kwargs) + record(py, RecordId::RelationFixtureCompletion, &kwargs) } pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_context.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_context.rs index d6509e6ecf..3d2187cabe 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_context.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_context.rs @@ -7,6 +7,7 @@ use sqlbuild_analysis::planner_fixtures::main::fixture_schema::fixture_schema; use crate::bindings::_helpers::planner_strategy::fixture_metadata::model_fixture_metadata; use crate::bindings::_helpers::planner_strategy::fixture_values::{columns, metadata, record}; +use crate::bindings::_helpers::records::ids::RecordId; #[pyfunction] #[pyo3(signature = (*, project, models_by_name = None))] @@ -35,7 +36,7 @@ pub(crate) fn build_relation_fixture_context<'py>( )?; kwargs.set_item("expected_types", base_expected_fixture_types(&relations)?)?; kwargs.set_item("relations", relations)?; - record(py, "RelationFixturePlanningContext", &kwargs) + record(py, RecordId::RelationFixturePlanningContext, &kwargs) } fn relation_metadata<'py>( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_reads.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_reads.rs index 6a4b719603..ce01565e3c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_reads.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_reads.rs @@ -16,6 +16,8 @@ use std::collections::{HashMap, HashSet}; use crate::bindings::_helpers::boundary::panics::value_error; use crate::bindings::_helpers::planner_strategy::fixture_values::columns; use crate::bindings::_helpers::planner_strategy::models::FixtureReadCacheHost; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::types::CompilerDetach; #[pyfunction] @@ -83,10 +85,8 @@ pub(crate) fn required_fixture_columns<'py>( stars: model_stars(&model, &schema, adapter)?, }); } - let owner = py - .import("sqlbuild._native")? - .getattr("_fixture_read_cache")? - .cast_into::()?; + let owner = + registry::native(py, NativeAttr::FixtureReadCache)?.cast_into::()?; let mut owner = owner.borrow_mut(); let cache = std::mem::take(&mut owner.cache); let fixture_keys: HashSet<(String, String)> = keys.keys().cloned().collect(); @@ -124,11 +124,11 @@ fn lineage_reads(model: &Bound<'_, PyAny>) -> PyResult> { } for fact in facts.try_iter()? { let fact = fact?; - if !fact.getattr("confidence")?.eq(model - .py() - .import("sqlbuild._native")? - .getattr("ColumnLineageConfidence")? - .getattr("HIGH")?)? + if !fact.getattr("confidence")?.eq(registry::class( + model.py(), + RecordId::ColumnLineageConfidence, + )? + .getattr("HIGH")?)? { continue; } @@ -218,10 +218,8 @@ pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { #[pyfunction] #[pyo3(signature = (*, reset = false))] fn fixture_read_cache_info(py: pyo3::Python<'_>, reset: bool) -> PyResult<(u64, usize)> { - let owner = py - .import("sqlbuild._native")? - .getattr("_fixture_read_cache")? - .cast_into::()?; + let owner = + registry::native(py, NativeAttr::FixtureReadCache)?.cast_into::()?; let mut owner = owner.borrow_mut(); if reset { owner.cache = Default::default(); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_types.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_types.rs index ff677ebe85..beb34dfd93 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_types.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_types.rs @@ -10,6 +10,8 @@ use sqlbuild_analysis::planner_fixtures::models::{ }; use crate::bindings::_helpers::planner_strategy::fixture_values::folded; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; #[pyfunction] #[pyo3(signature = (*, base_expected_types, ordered_model_names, model_map))] @@ -80,11 +82,7 @@ fn projections(model: &Bound<'_, PyAny>) -> PyResult> if lineage.is_none() { return Ok(projections); } - let direct = model - .py() - .import("sqlbuild._native")? - .getattr("ColumnTransformKind")? - .getattr("DIRECT")?; + let direct = registry::class(model.py(), RecordId::ColumnTransformKind)?.getattr("DIRECT")?; for column in lineage.try_iter()? { let column = column?; if !column.getattr("transform_kind")?.eq(&direct)? { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_values.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_values.rs index f22fa7a1ec..2177f21723 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_values.rs @@ -1,11 +1,11 @@ //! Fixture boundary records and adapter callbacks. +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; use sqlbuild_analysis::planner_fixtures::models::FixtureColumn; -pub(crate) const MODELS: &str = "sqlbuild._native"; - pub(crate) fn folded(value: &Bound<'_, PyAny>) -> PyResult { value.call_method0("casefold")?.extract() } @@ -36,10 +36,10 @@ pub(crate) fn columns(values: &Bound<'_, PyAny>, inferred: bool) -> PyResult( py: Python<'py>, - name: &str, + name: RecordId, kwargs: &Bound<'py, PyDict>, ) -> PyResult> { - py.import(MODELS)?.getattr(name)?.call((), Some(kwargs)) + registry::class(py, name)?.call((), Some(kwargs)) } pub(crate) fn metadata<'py>( @@ -54,13 +54,13 @@ pub(crate) fn metadata<'py>( kwargs.set_item("name", &column.name)?; kwargs.set_item("type", &column.sql_type)?; kwargs.set_item("nullable", column.nullable)?; - record(py, "FixtureColumnMetadata", &kwargs) + record(py, RecordId::FixtureColumnMetadata, &kwargs) }) .collect::>>()?; let kwargs = PyDict::new(py); kwargs.set_item("columns", PyTuple::new(py, values)?)?; kwargs.set_item("authoritative_names", authoritative)?; - record(py, "FixtureRelationMetadata", &kwargs) + record(py, RecordId::FixtureRelationMetadata, &kwargs) } pub(crate) fn label(key: &Bound<'_, PyAny>) -> PyResult { @@ -78,5 +78,5 @@ pub(crate) fn diagnostic<'py>( let kwargs = PyDict::new(key.py()); kwargs.set_item("key", key)?; kwargs.set_item("message", message)?; - record(key.py(), "RelationFixtureDiagnostic", &kwargs) + record(key.py(), RecordId::RelationFixtureDiagnostic, &kwargs) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/lineage.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/lineage.rs index 1a7d6f905c..9fc4edac60 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/lineage.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/lineage.rs @@ -17,9 +17,8 @@ use crate::bindings::_helpers::model_config::config_values::config_str; use crate::bindings::_helpers::planner_strategy::microbatches::{ CAP_ACTIONS, microbatch_limit_config, }; -use crate::bindings::_helpers::planner_strategy::values::{ - COMPILE_EXCEPTIONS_MODULE, PLANNER_EXCEPTIONS_MODULE, input_error, text, -}; +use crate::bindings::_helpers::planner_strategy::values::{input_error, text}; +use crate::bindings::_helpers::records::ids::ErrorId; const MICROBATCH_MODE: &str = "microbatch"; @@ -106,7 +105,7 @@ fn lineage_error(py: Python<'_>, failure: LineageFailure) -> PyErr { LineageFailure::Graph(error) => error, LineageFailure::NotInLineage { model, input } => input_error( py, - (PLANNER_EXCEPTIONS_MODULE, "PlannerInputError"), + ErrorId::PlannerInput, &LineageFailure::::message(&model, &input), Some(LineageFailure::::code()), ), @@ -158,7 +157,7 @@ fn reject_capped_watermark_inputs<'py>( Ok(None) => Ok(()), Ok(Some(capped)) => Err(input_error( py, - (COMPILE_EXCEPTIONS_MODULE, "CompileInputError"), + ErrorId::CompileInput, &capped.message(), None, )), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/microbatch_limits.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/microbatch_limits.rs index f63f1dfafb..9e99296361 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/microbatch_limits.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/microbatch_limits.rs @@ -11,16 +11,15 @@ use sqlbuild_core::plan_display::errors::UnknownCursorGrain; use sqlbuild_core::plan_display::main::effective_timestamp_grain::effective_timestamp_grain; use sqlbuild_core::plan_display::models::TimestampGrainFacts; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::compiled_project::records::replace; use crate::bindings::_helpers::plan_output::display::resolve_effective_microbatch_batch_size; -use crate::bindings::_helpers::planner_strategy::constants::PLANNER_TYPES_MODULE; use crate::bindings::_helpers::planner_strategy::microbatches::{ - CAP_ACTIONS, CONTRACT_TYPES_MODULE, capped_microbatch_bounds, count_microbatches, -}; -use crate::bindings::_helpers::planner_strategy::values::{ - COMPILE_EXCEPTIONS_MODULE, input_error, optional_string, text, + CAP_ACTIONS, capped_microbatch_bounds, count_microbatches, }; +use crate::bindings::_helpers::planner_strategy::values::{input_error, optional_string, text}; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; const ERROR_ACTION: &str = "error"; const SKIP_ACTION: &str = "skip"; @@ -33,7 +32,7 @@ type LimitedEntry<'py> = (Bound<'py, PyAny>, Option>); /// `CursorGrain(grain)`'s error for a grain outside the known grains. pub(crate) fn unknown_grain(py: Python<'_>, error: UnknownCursorGrain) -> PyErr { - imported(py, PLANNER_TYPES_MODULE, "CursorGrain") + registry::class(py, RecordId::CursorGrain) .and_then(|class| class.call1((error.0,))) .err() .unwrap_or_else(|| pyo3::exceptions::PyValueError::new_err("unknown cursor grain")) @@ -149,18 +148,16 @@ fn limited<'py>( return Ok((limited_entry, None)); }; if action_text == ERROR_ACTION { - return Err(input_error( - py, - (COMPILE_EXCEPTIONS_MODULE, "CompileInputError"), - &message, - None, - )); + return Err(input_error(py, ErrorId::CompileInput, &message, None)); } let plan_warning: Bound<'py, PyAny> = call_with( - &imported(py, "sqlbuild._native", "PlanWarning")?, + ®istry::class(py, RecordId::PlanWarning)?, &[ ("model_name", name), - ("severity", native_enum(py, "WarningSeverity", "warning")?), + ( + "severity", + native_enum(py, RecordId::WarningSeverity, "warning")?, + ), ( "code", PyString::new(py, MICROBATCH_LIMIT_WARNING_CODE).into_any(), @@ -238,7 +235,7 @@ pub(crate) fn apply_configured_microbatch_limits<'py>( .is_truthy()? { let error: Bound<'py, PyAny> = - imported(py, CONTRACT_TYPES_MODULE, "MicrobatchLimitAction")?.getattr("ERROR")?; + registry::class(py, RecordId::MicrobatchLimitAction)?.getattr("ERROR")?; return with_warnings(limited(entry, configured, error)?, Vec::new()); } let declared: Bound<'py, PyAny> = entry.getattr("declared_microbatch_limit_action")?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/microbatches.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/microbatches.rs index 596c9526f3..270bfe373d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/microbatches.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/microbatches.rs @@ -18,14 +18,13 @@ use sqlbuild_analysis::planner_strategy::types::Timeline; use sqlbuild_cursor::algebra::main::parse_duration::parse_duration; use sqlbuild_cursor::algebra::models::{DurationDigits, DurationSpan}; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::cursor::durations::shifted_moment; use crate::bindings::_helpers::plan_output::display::aligned_timestamp_bounds; -use crate::bindings::_helpers::planner_changes::results::MODELS_MODULE; use crate::bindings::_helpers::planner_strategy::values::text; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const CURSOR_MODELS_MODULE: &str = "sqlbuild._native"; -pub(crate) const CONTRACT_TYPES_MODULE: &str = "sqlbuild._native"; const INTEGER_CURSOR: &str = "integer"; const TIMESTAMP_CURSOR: &str = "timestamp"; const MONTHS_PER_YEAR: i64 = 12; @@ -80,14 +79,14 @@ fn duration_span(batch_size: &str) -> Option { /// The bounds of a `CursorBounds` when each is an instance of one of the cursor value `classes`. fn typed_bounds<'py>( bounds: &Bound<'py, PyAny>, - classes: &[&str], + classes: &[RecordId], ) -> PyResult>> { let py: Python<'py> = bounds.py(); let (start, end) = (bounds.getattr("start")?, bounds.getattr("end")?); for value in [&start, &end] { let mut typed: bool = false; for class in classes { - typed = typed || value.is_instance(&imported(py, CURSOR_MODELS_MODULE, class)?)?; + typed = typed || value.is_instance(®istry::class(py, *class)?)?; } if !typed { return Ok(None); @@ -98,7 +97,7 @@ fn typed_bounds<'py>( /// The integer values of integer bounds. fn integer_bounds(bounds: &Bound<'_, PyAny>) -> PyResult> { - let Some((start, end)) = typed_bounds(bounds, &["IntegerValue"])? else { + let Some((start, end)) = typed_bounds(bounds, &[RecordId::IntegerValue])? else { return Ok(None); }; Ok(Some(( @@ -115,7 +114,9 @@ fn timeline_bounds<'py>( let Some(span) = duration_span(batch_size) else { return Ok(None); }; - let Some((start, end)) = typed_bounds(bounds, &["DateValue", "TimestampValue"])? else { + let Some((start, end)) = + typed_bounds(bounds, &[RecordId::DateValue, RecordId::TimestampValue])? + else { return Ok(None); }; let aligned: Bound<'py, PyTuple> = aligned_timestamp_bounds(&start, &end)?; @@ -156,7 +157,7 @@ fn cursor_bounds<'py>( ) -> PyResult> { let py: Python<'py> = start.py(); call_with( - &imported(py, MODELS_MODULE, "CursorBounds")?, + ®istry::class(py, RecordId::CursorBounds)?, &[("start", start), ("end", end)], ) } @@ -164,13 +165,10 @@ fn cursor_bounds<'py>( /// `(value=value)` of the cursor values module. fn cursor_value<'py>( py: Python<'py>, - class: &str, + class: RecordId, value: Bound<'py, PyAny>, ) -> PyResult> { - call_with( - &imported(py, CURSOR_MODELS_MODULE, class)?, - &[("value", value)], - ) + call_with(®istry::class(py, class)?, &[("value", value)]) } /// One concrete range capped to `max_batches` batch windows from the end the action keeps. @@ -197,12 +195,12 @@ pub(crate) fn capped_microbatch_bounds<'py>( match capped { CappedBound::End(value) => CappedBound::End(cursor_value( py, - "IntegerValue", + RecordId::IntegerValue, value.into_pyobject(py)?.into_any(), )?), CappedBound::Start(value) => CappedBound::Start(cursor_value( py, - "IntegerValue", + RecordId::IntegerValue, value.into_pyobject(py)?.into_any(), )?), } @@ -214,9 +212,11 @@ pub(crate) fn capped_microbatch_bounds<'py>( return Ok(bounds); }; match cap_timeline_bounds(&timeline, range, max_batches, direction)? { - CappedBound::End(value) => CappedBound::End(cursor_value(py, "TimestampValue", value)?), + CappedBound::End(value) => { + CappedBound::End(cursor_value(py, RecordId::TimestampValue, value)?) + } CappedBound::Start(value) => { - CappedBound::Start(cursor_value(py, "TimestampValue", value)?) + CappedBound::Start(cursor_value(py, RecordId::TimestampValue, value)?) } } }; @@ -269,7 +269,7 @@ pub(crate) fn microbatch_limit_config<'py>( .filter(|action| action.is_instance_of::()); if let (Some(max_batches), Some(action)) = (max_batches, action) { let action: Bound<'py, PyAny> = - imported(py, CONTRACT_TYPES_MODULE, "MicrobatchLimitAction")?.call1((action,))?; + registry::class(py, RecordId::MicrobatchLimitAction)?.call1((action,))?; return Ok((Some(max_batches), Some(action))); } } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/model_entries.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/model_entries.rs index 8ed737996f..0b69c266ef 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/model_entries.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/model_entries.rs @@ -10,21 +10,23 @@ use sqlbuild_analysis::planner_strategy::main::table_type_ddl::table_type_ddl; use sqlbuild_analysis::planner_strategy::models::LookbackConfig; use crate::bindings::_helpers::boundary::keyword_request::Request; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::cursor::scalars::sentinel_to_token; use crate::bindings::_helpers::model_config::config_values::{ config_bool, config_cursor_bound, config_int, config_str, config_string_tuple, }; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_strategy::constants::{ - ADAPTER_MODELS_MODULE, CURSOR_TYPES_MODULE, CUSTOM_MATERIALIZATION, DDL_SEPARATOR, - ENFORCED_CONTRACT, MICROBATCH_MODE, NATIVE_MODULE, PLAN_MODELS_MODULE, PLANNER_TYPES_MODULE, + CUSTOM_MATERIALIZATION, DDL_SEPARATOR, ENFORCED_CONTRACT, MICROBATCH_MODE, }; use crate::bindings::_helpers::planner_strategy::cursor_inputs::model_values; use crate::bindings::_helpers::planner_strategy::microbatch_limits::{replay_grain, unknown_grain}; use crate::bindings::_helpers::planner_strategy::microbatches::microbatch_limit_config; -use crate::bindings::_helpers::planner_strategy::values::{ - PLANNER_EXCEPTIONS_MODULE, input_error, text, -}; +use crate::bindings::_helpers::planner_strategy::values::{input_error, text}; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; /// Model config keys copied to the plan entry as optional strings, with their entry fields. const STRING_FIELDS: [(&str, &str); 15] = [ @@ -106,7 +108,7 @@ fn schema_columns<'py>( if entry.is_none() { return Ok(PyTuple::empty(py)); } - let class: Bound<'py, PyAny> = imported(py, ADAPTER_MODELS_MODULE, "ColumnInfo")?; + let class: Bound<'py, PyAny> = host::entry(py, AdapterHost::ColumnInfo)?; let mut columns: Vec> = Vec::new(); for column in entry.getattr("columns")?.try_iter()? { let column: Bound<'py, PyAny> = column?; @@ -152,7 +154,7 @@ fn on_schema_change<'py>(model: &Bound<'py, PyAny>) -> PyResult() { - match imported(py, PLANNER_TYPES_MODULE, "OnSchemaChange")?.call1((&raw,)) { + match registry::class(py, RecordId::OnSchemaChange)?.call1((&raw,)) { Ok(policy) => return Ok(policy), Err(error) if !error.is_instance_of::(py) => return Err(error), Err(_) => {} @@ -160,7 +162,7 @@ fn on_schema_change<'py>(model: &Bound<'py, PyAny>) -> PyResult( let model: Bound<'py, PyAny> = request.get("model")?; let name: Bound<'py, PyAny> = model.getattr("name")?; let mut warnings: Vec> = call_with( - &imported(py, NATIVE_MODULE, "build_model_warnings")?, + ®istry::native(py, NativeAttr::BuildModelWarnings)?, &[ ("model_name", name.clone()), ("materialization_type", request.get("materialization_type")?), @@ -345,7 +347,7 @@ fn model_warnings<'py>( .try_iter()? .collect::>()?; let cursor_warning: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "check_cursor_type_consistency")?, + ®istry::native(py, NativeAttr::CheckCursorTypeConsistency)?, &[ ("model_name", name), ("cursor_column", request.get("cursor_column")?), @@ -371,9 +373,9 @@ fn ddl_cursor_bounds<'py>(request: &Request<'_, 'py>) -> PyResult = imported(py, CURSOR_TYPES_MODULE, "BoundSentinel")?; + let sentinel: Bound<'py, PyAny> = host::entry(py, ExecutorHost::BoundSentinel)?; call_with( - &imported(py, PLAN_MODELS_MODULE, "CursorBounds")?, + ®istry::class(py, RecordId::CursorBounds)?, &[ ("start", sentinel.getattr("START")?), ("end", sentinel.getattr("END")?), @@ -491,7 +493,7 @@ pub(crate) fn assemble_model_plan_entry<'py>( let name: Bound<'py, PyAny> = model.getattr("name")?; let policy: Bound<'py, PyAny> = on_schema_change(&model)?; let schema_actions: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "resolve_schema_actions")?, + ®istry::native(py, NativeAttr::ResolveSchemaActions)?, &[ ("schema_findings", change.getattr("schema_findings")?), ("on_schema_change", policy.clone()), @@ -560,6 +562,6 @@ pub(crate) fn assemble_model_plan_entry<'py>( &entry, )?; let plan_entry: Bound<'py, PyAny> = - imported(py, PLAN_MODELS_MODULE, "ModelPlanEntry")?.call((), Some(&entry))?; + registry::class(py, RecordId::ModelPlanEntry)?.call((), Some(&entry))?; PyTuple::new(py, [plan_entry, warnings.into_any()]) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/strategy.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/strategy.rs index ba5ea4535a..389a5f139b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/strategy.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/strategy.rs @@ -13,14 +13,14 @@ use sqlbuild_analysis::planner_strategy::models::{ ModelStrategy, PlanAction, PlanReason, SchemaActionKind, WarningInputs, }; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::planner_changes::results::planner_enum; use crate::bindings::_helpers::planner_strategy::values::{ - PLANNER_EXCEPTIONS_MODULE, change_facts, config_value, finding_rows, input_error, - materialization_value, model_materialization, optional_string, plan_warning, text, + change_facts, config_value, finding_rows, input_error, materialization_value, + model_materialization, optional_string, plan_warning, text, }; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; fn action_value(action: PlanAction) -> &'static str { match action { @@ -64,7 +64,7 @@ fn schema_policy(on_schema_change: &Bound<'_, PyAny>) -> PyResult fn materialization_of<'py>(model: &Bound<'py, PyAny>) -> PyResult> { planner_enum( model.py(), - "MaterializationType", + RecordId::MaterializationType, materialization_value(model_materialization(model)?), ) } @@ -90,7 +90,7 @@ fn resolve_model_plan_action<'py>( |error| { input_error( py, - (PLANNER_EXCEPTIONS_MODULE, "PlannerInputError"), + ErrorId::PlannerInput, &error.message(), Some(error.code()), ) @@ -99,8 +99,8 @@ fn resolve_model_plan_action<'py>( PyTuple::new( py, [ - planner_enum(py, "PlanAction", action_value(action))?, - planner_enum(py, "PlanReason", reason_value(reason))?, + planner_enum(py, RecordId::PlanAction, action_value(action))?, + planner_enum(py, RecordId::PlanReason, reason_value(reason))?, ], ) } @@ -113,7 +113,7 @@ fn resolve_schema_actions<'py>( on_schema_change: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = schema_findings.py(); - let class: Bound<'py, PyAny> = imported(py, "sqlbuild._native", "SchemaAction")?; + let class: Bound<'py, PyAny> = registry::class(py, RecordId::SchemaAction)?; let policy: Option = schema_policy(on_schema_change)?; let mut actions: Vec> = Vec::new(); for row in schema_actions(&finding_rows(schema_findings)?, policy.as_deref()) { @@ -125,7 +125,7 @@ fn resolve_schema_actions<'py>( actions.push(call_with( &class, &[ - ("kind", native_enum(py, "SchemaActionKind", kind)?), + ("kind", native_enum(py, RecordId::SchemaActionKind, kind)?), ( "column_name", PyString::new(py, &row.column_name).into_any(), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/values.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/values.rs index 14815320f1..0617f5ce8e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/values.rs @@ -13,10 +13,10 @@ use sqlbuild_analysis::planner_strategy::models::{ ChangeFacts, MaterializationType, PlanChangeKind, WarningRow, WarningSeverity, }; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; -pub(crate) const PLANNER_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; -pub(crate) const COMPILE_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; const YML_SOURCE: &str = "yml"; const FULL_BACKFILL: &str = "full"; const FORWARD_ONLY_BACKFILL: &str = "forward"; @@ -137,24 +137,27 @@ pub(crate) fn plan_warning<'py>( WarningSeverity::Info => "info", }; call_with( - &imported(py, "sqlbuild._native", "PlanWarning")?, + ®istry::class(py, RecordId::PlanWarning)?, &[ ("model_name", model_name.clone()), - ("severity", native_enum(py, "WarningSeverity", severity)?), + ( + "severity", + native_enum(py, RecordId::WarningSeverity, severity)?, + ), ("message", PyString::new(py, &row.message).into_any()), ("code", row.code.into_pyobject(py)?.into_any()), ], ) } -/// `.(message, code=code)` raised as an error. +/// The native error `class(message, code=code)`. pub(crate) fn input_error( py: Python<'_>, - (module, class): (&str, &str), + class: ErrorId, message: &str, code: Option<&str>, ) -> PyErr { - let raised: PyResult> = imported(py, module, class).and_then(|class| { + let raised: PyResult> = registry::error(py, class).and_then(|class| { let options: Bound<'_, PyDict> = PyDict::new(py); if let Some(code) = code { options.set_item("code", code)?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/constants.rs index 49765eee0a..7a9b13e5d2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/constants.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/constants.rs @@ -1,51 +1,28 @@ //! Python modules and fixed names of warehouse inspection for planning. -pub(crate) const RELATIONS_MAIN_LIST: (&str, &str) = ( - "sqlbuild.adapter.relations.main._list_relations_for_inspection", - "list_relations_for_inspection", -); -pub(crate) const RELATIONS_MAIN_COLUMNS: (&str, &str) = ( - "sqlbuild.adapter.relations.main._get_columns_for_inspection", - "get_columns_for_inspection", -); -pub(crate) const ACTIVE_CATALOG: (&str, &str) = ( - "sqlbuild.adapter.relations.main.active_inspection_catalog", - "active_inspection_catalog", -); -pub(crate) const RUN_BOUNDED: (&str, &str) = ( - "sqlbuild.adapter.relations.main.run_bounded_inspections", - "run_bounded_inspections", -); -pub(crate) const RECORD_QUERY: (&str, &str) = ( - "sqlbuild.adapter.relations.main.record_inspection_query", - "record_inspection_query", -); -pub(crate) const RUN_RECORDED_QUERY: (&str, &str) = ( - "sqlbuild.adapter.relations.main.run_recorded_inspection_query", - "run_recorded_inspection_query", -); -pub(crate) const QUERY_RECORD: (&str, &str) = - ("sqlbuild.adapter.relations.models", "InspectionQueryRecord"); -pub(crate) const COLUMN_INFO: (&str, &str) = ("sqlbuild.adapter.contract.models", "ColumnInfo"); -pub(crate) const RELATION_LOOKUP: (&str, &str) = - ("sqlbuild.adapter.contract.models", "RelationLookup"); -pub(crate) const LOG_DEBUG_EVENT: (&str, &str) = ( - "sqlbuild.diagnostics.main.log_debug_event", - "log_debug_event", -); -pub(crate) const CURSOR_RESOLVE_MODULE: &str = "sqlbuild._native"; -pub(crate) const CURSOR_POLICIES: (&str, &str) = - ("sqlbuild._native", "resolve_start_cursor_config"); -pub(crate) const MAXIMUM_START: (&str, &str) = ("sqlbuild._native", "maximum_allowed_start"); -pub(crate) const RENDER_SOURCE_READ: (&str, &str) = - ("sqlbuild._native", "render_source_read_relation"); -pub(crate) const YAML_ENTRY_LINE: (&str, &str) = ("sqlbuild._native", "yaml_entry_line"); -pub(crate) const RUN_SCOPE: (&str, &str) = - ("sqlbuild.runtime.observability.main.run_scope", "run_scope"); -pub(crate) const SQL_HOOK_ENTRY: (&str, &str) = ("sqlbuild._native", "SqlHookEntry"); +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; + +pub(crate) const RELATIONS_MAIN_LIST: AdapterHost = AdapterHost::ListRelationsForInspection; +pub(crate) const RELATIONS_MAIN_COLUMNS: AdapterHost = AdapterHost::GetColumnsForInspection; +pub(crate) const ACTIVE_CATALOG: AdapterHost = AdapterHost::ActiveInspectionCatalog; +pub(crate) const RUN_BOUNDED: AdapterHost = AdapterHost::RunBoundedInspections; +pub(crate) const RECORD_QUERY: AdapterHost = AdapterHost::RecordInspectionQuery; +pub(crate) const RUN_RECORDED_QUERY: AdapterHost = AdapterHost::RunRecordedInspectionQuery; +pub(crate) const QUERY_RECORD: AdapterHost = AdapterHost::InspectionQueryRecord; +pub(crate) const COLUMN_INFO: AdapterHost = AdapterHost::ColumnInfo; +pub(crate) const RELATION_LOOKUP: AdapterHost = AdapterHost::RelationLookup; +pub(crate) const LOG_DEBUG_EVENT: ExecutorHost = ExecutorHost::LogDebugEvent; +pub(crate) const CURSOR_POLICIES: NativeAttr = NativeAttr::ResolveStartCursorConfig; +pub(crate) const MAXIMUM_START: NativeAttr = NativeAttr::MaximumAllowedStart; +pub(crate) const RENDER_SOURCE_READ: NativeAttr = NativeAttr::RenderSourceReadRelation; +pub(crate) const YAML_ENTRY_LINE: NativeAttr = NativeAttr::YamlEntryLine; +pub(crate) const RUN_SCOPE: ExecutorHost = ExecutorHost::RunScope; +pub(crate) const SQL_HOOK_ENTRY: RecordId = RecordId::SqlHookEntry; pub(crate) const CURSOR_ALGEBRA_ERROR: (&str, &str) = ("sqlbuild.cursor_algebra.exceptions", "CursorAlgebraError"); -pub(crate) const DATE_VALUE: (&str, &str) = ("sqlbuild._native", "DateValue"); +pub(crate) const DATE_VALUE: RecordId = RecordId::DateValue; pub(crate) const PLANNER_LOGGER: &str = "sqlbuild.planner"; pub(crate) const METADATA_NAME_FILTER_LIMIT: usize = 250; pub(crate) const FUNCTION_NODE_TYPES: [&str; 2] = ["udf", "table_fn"]; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/cursors.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/cursors.rs index 5b7f203d9b..f316e0e19d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/cursors.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/cursors.rs @@ -1,20 +1,22 @@ //! Cursor MIN/MAX inspection of selected incremental models and their watermark inputs. use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; -use crate::bindings::_helpers::planner_phases::constants::PLANNER_TYPES; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_phases::objects::{ - by_name, config_str, member, model_key, native, optional_text, planner_error, planner_model, - text, tuple, + by_name, config_str, model_key, native, native_member, optional_text, planner_error, + planner_model, text, tuple, }; use crate::bindings::_helpers::planner_warehouse::constants::{ - CURSOR_ALGEBRA_ERROR, CURSOR_POLICIES, CURSOR_RESOLVE_MODULE, CURSOR_TYPE_INTEGER, - CURSOR_TYPE_TIMESTAMP, DATE_VALUE, LOG_DEBUG_EVENT, MAXIMUM_START, PLANNER_LOGGER, - QUERY_RECORD, RECORD_QUERY, RUN_BOUNDED, RUN_RECORDED_QUERY, + CURSOR_ALGEBRA_ERROR, CURSOR_POLICIES, CURSOR_TYPE_INTEGER, CURSOR_TYPE_TIMESTAMP, DATE_VALUE, + LOG_DEBUG_EVENT, MAXIMUM_START, PLANNER_LOGGER, QUERY_RECORD, RECORD_QUERY, RUN_BOUNDED, + RUN_RECORDED_QUERY, }; use crate::bindings::_helpers::planner_warehouse::snapshot::{ SnapshotRequest, inspection_concurrency, }; use crate::bindings::_helpers::planner_warehouse::watermarks::validate_watermark_column; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::IntoPyObject; use pyo3::exceptions::PyException; use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyErr, PyResult, Python}; @@ -95,17 +97,21 @@ fn parse<'py>( ) -> PyResult> { native( py, - "cursor_parse", + NativeAttr::CursorParse, &[("raw", raw.clone()), ("cursor_type", text(py, cursor_type))], ) } fn render<'py>(value: &Bound<'py, PyAny>) -> PyResult> { - native(value.py(), "cursor_render", &[("value", value.clone())]) + native( + value.py(), + NativeAttr::CursorRender, + &[("value", value.clone())], + ) } fn grain<'py>(py: Python<'py>, value: &str) -> PyResult> { - member(py, (PLANNER_TYPES, "CursorGrain"), value) + native_member(py, RecordId::CursorGrain, value) } fn parse_warehouse_cursor<'py>(value: &Bound<'py, PyAny>) -> PyResult> { @@ -132,10 +138,7 @@ fn log_debug<'py>( let logger = imported(py, "logging", "getLogger")?.call1((PLANNER_LOGGER,))?; let mut fields = vec![("logger", logger), ("message", text(py, message))]; fields.extend(context.iter().cloned()); - call_with( - &imported(py, LOG_DEBUG_EVENT.0, LOG_DEBUG_EVENT.1)?, - &fields, - )?; + call_with(&host::entry(py, LOG_DEBUG_EVENT)?, &fields)?; Ok(()) } @@ -236,7 +239,7 @@ fn upstream_relation<'py>( { return native( py, - "render_source_relation", + NativeAttr::RenderSourceRelation, &[ ("entry", source.getattr("source_entry")?), ("adapter", adapter.clone()), @@ -253,7 +256,7 @@ fn lineage_reference<'py>( ) -> PyResult> { native( model.py(), - "resolve_lineage_reference", + NativeAttr::ResolveLineageReference, &[ ("model", model.clone()), ("input_name", input_name.clone()), @@ -277,7 +280,7 @@ fn terminal_bound<'py>( }; let value = native( py, - "get_config_cursor_bound", + NativeAttr::GetConfigCursorBound, &[ ("values", upstream.getattr("config")?.getattr("values")?), ("key", text(py, key)), @@ -359,7 +362,7 @@ fn collect_models<'py>( }; let watermark_inputs = native( py, - "resolve_cursor_input_roles", + NativeAttr::ResolveCursorInputRoles, &[("model", model.clone())], )? .getattr("watermark_inputs")?; @@ -457,11 +460,7 @@ fn collect_models<'py>( }); } let effective_grain = call_with( - &imported( - py, - CURSOR_RESOLVE_MODULE, - "resolve_effective_timestamp_grain", - )?, + ®istry::native(py, NativeAttr::ResolveEffectiveTimestampGrain)?, &[ ("cursor_type", optional_text(py, cursor_type.as_deref())), ( @@ -478,7 +477,7 @@ fn collect_models<'py>( let mode = config_str(&model, "cursor_watermark_mode")? .filter(|value| !value.is_empty()) .unwrap_or_else(|| "all".to_owned()); - let watermark_mode = member(py, ("sqlbuild._native", "CursorWatermarkMode"), &mode)?; + let watermark_mode = native_member(py, RecordId::CursorWatermarkMode, &mode)?; models.push(CursorModel { target_tag, target_relation, @@ -493,7 +492,7 @@ fn collect_models<'py>( upstreams, effective_grain, start_cursor_config: call_with( - &imported(py, CURSOR_POLICIES.0, CURSOR_POLICIES.1)?, + ®istry::native(py, CURSOR_POLICIES)?, &[ ("model", model.clone()), ("project_config", inputs.start_cursor_config.clone()), @@ -584,7 +583,7 @@ fn read_cursor_query<'py>( error.str()?.into_any() }; let record = call_with( - &imported(py, QUERY_RECORD.0, QUERY_RECORD.1)?, + &host::entry(py, QUERY_RECORD)?, &[ ("sql", text(py, sql)), ("elapsed_seconds", PyFloat::new(py, elapsed).into_any()), @@ -592,10 +591,7 @@ fn read_cursor_query<'py>( ("error", error_text), ], )?; - call_with( - &imported(py, RECORD_QUERY.0, RECORD_QUERY.1)?, - &[("record", record)], - )?; + call_with(&host::entry(py, RECORD_QUERY)?, &[("record", record)])?; Ok(PyTuple::new(py, [row, PyFloat::new(py, elapsed).into_any(), error])?.into_any()) } @@ -746,7 +742,7 @@ fn execute_queries<'py>( tasks.push(bound_task(py, cursor_query_sql(query), handles)?); } call_with( - &imported(py, RUN_BOUNDED.0, RUN_BOUNDED.1)?, + &host::entry(py, RUN_BOUNDED)?, &[ ("tasks", tuple(py, tasks)?), ("concurrency", concurrency.into_pyobject(py)?.into_any()), @@ -805,7 +801,7 @@ fn eligible_task<'py>( }, )?; let rows = call_with( - &imported(py, RUN_RECORDED_QUERY.0, RUN_RECORDED_QUERY.1)?, + &host::entry(py, RUN_RECORDED_QUERY)?, &[("sql", text(py, &sql)), ("run", run.into_any())], ); match rows { @@ -855,7 +851,7 @@ fn eligible_target_maxes<'py>( continue; } let allowed = call_with( - &imported(py, MAXIMUM_START.0, MAXIMUM_START.1)?, + ®istry::native(py, MAXIMUM_START)?, &[ ("discovered_value", render(target_max)?), ("cursor_grain", info.effective_grain.clone()), @@ -866,7 +862,7 @@ fn eligible_target_maxes<'py>( let horizon = parse(py, &allowed, CURSOR_TYPE_TIMESTAMP)?; let order: i64 = native( py, - "cursor_compare", + NativeAttr::CursorCompare, &[("left", target_max.clone()), ("right", horizon.clone())], )? .extract()?; @@ -889,7 +885,7 @@ fn eligible_target_maxes<'py>( ( "is_date", horizon - .is_instance(&imported(py, DATE_VALUE.0, DATE_VALUE.1)?)? + .is_instance(®istry::class(py, DATE_VALUE)?)? .into_pyobject(py)? .to_owned() .into_any(), @@ -912,7 +908,7 @@ fn eligible_target_maxes<'py>( )?); } let rows_by_request = call_with( - &imported(py, RUN_BOUNDED.0, RUN_BOUNDED.1)?, + &host::entry(py, RUN_BOUNDED)?, &[ ("tasks", tuple(py, tasks)?), ( @@ -939,13 +935,13 @@ fn eligible_target_maxes<'py>( .unwrap_or(CURSOR_TYPE_TIMESTAMP); let value = parse(py, &first, cursor_type)?; let normalized = call_with( - &imported(py, CURSOR_RESOLVE_MODULE, "normalize_cursor_snapshot_grain")?, + ®istry::native(py, NativeAttr::NormalizeCursorSnapshotGrain)?, &[ ( "cursor_snapshot", planner_model( py, - "ModelCursorSnapshot", + RecordId::ModelCursorSnapshot, &[ ("target_max", py.None().into_bound(py)), ("upstream_mins", PyTuple::empty(py).into_any()), @@ -1035,7 +1031,7 @@ fn assemble<'py>( let effective_max = match &upstream.terminal_end { Some(end) => Some(native( py, - "cursor_exclusive_to_inclusive", + NativeAttr::CursorExclusiveToInclusive, &[ ("value", end.clone()), ("grain", cursor_grain(py, info, upstream)?), @@ -1060,7 +1056,7 @@ fn assemble<'py>( }; evidence.push(planner_model( py, - "CursorInputEvidence", + RecordId::CursorInputEvidence, &[ ("relation", text(py, &upstream.relation)), ("cursor_column", text(py, &upstream.cursor_column)), @@ -1095,7 +1091,7 @@ fn assemble<'py>( available = Some( native( py, - "cursor_observed_partition", + NativeAttr::CursorObservedPartition, &[ ("value", max_value.clone()), ("grain", cursor_grain(py, info, upstream)?), @@ -1118,7 +1114,7 @@ fn assemble<'py>( &info.model_name, planner_model( py, - "ModelCursorSnapshot", + RecordId::ModelCursorSnapshot, &[ ("target_max", optional(py, target_max)), ("upstream_mins", tuple(py, mins)?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/fingerprint_cache.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/fingerprint_cache.rs index 7f970753fc..7fe85474b5 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/fingerprint_cache.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/fingerprint_cache.rs @@ -4,6 +4,8 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, }; use crate::bindings::_helpers::planner_phases::objects::{native, optional_text, text}; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use pyo3::exceptions::{PyOSError, PyValueError}; use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyErr, PyResult, Python}; use pyo3::types::{PyBool, PyDict, PyFloat, PyList, PyListMethods, PyTuple}; @@ -274,17 +276,16 @@ impl MigrationFingerprintCache { let metadata_json: String = args.value("metadata_json")?.extract()?; let ref_identities = &args.value("ref_identities")?; let dialect: Option = args.value("dialect")?.extract()?; - let metadata_json: String = - imported(py, "sqlbuild._native", "without_declared_columns_hash")? - .call1((metadata_json,))? - .extract()?; + let metadata_json: String = registry::native(py, NativeAttr::WithoutDeclaredColumnsHash)? + .call1((metadata_json,))? + .extract()?; let definition = (query_sql.clone(), metadata_json.clone()); let names = match self.ref_names.get(&definition) { Some(names) => names.clone(), None => { let found = native( py, - "migration_fingerprint_ref_names", + NativeAttr::MigrationFingerprintRefNames, &[ ("query_sql", text(py, &query_sql)), ("metadata_json", text(py, &metadata_json)), @@ -320,7 +321,7 @@ impl MigrationFingerprintCache { } let computed: Option = native( py, - "build_migration_fingerprint", + NativeAttr::BuildMigrationFingerprint, &[ ("query_sql", text(py, &key.0)), ("metadata_json", text(py, &key.1)), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/semantic_sources.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/semantic_sources.rs index 747681446e..f91f79d345 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/semantic_sources.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/semantic_sources.rs @@ -1,7 +1,8 @@ //! Complete missing source metadata in bounded, database-grouped batches. use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_phases::objects::{text, tuple}; use crate::bindings::_helpers::planner_warehouse::constants::ACTIVE_CATALOG; use crate::bindings::_helpers::planner_warehouse::snapshot::{columns_for, list_relations}; @@ -54,7 +55,7 @@ fn source_candidates<'py>( let mut pending: Vec> = entries.to_vec(); let mut relations: Vec> = Vec::new(); let catalog = call_with( - &imported(py, ACTIVE_CATALOG.0, ACTIVE_CATALOG.1)?, + &host::entry(py, ACTIVE_CATALOG)?, &[ ("adapter", adapter.clone()), ("connection", connection.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/snapshot.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/snapshot.rs index 81872bf21a..40177197f8 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/snapshot.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/snapshot.rs @@ -1,7 +1,8 @@ //! Gather a frozen point-in-time warehouse snapshot for planning. use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_phases::full_refresh::effectively_full_refreshed_model_names; use crate::bindings::_helpers::planner_phases::objects::{ by_name, frozenset, model_key, native, text, tuple, @@ -11,6 +12,8 @@ use crate::bindings::_helpers::planner_warehouse::constants::{ RELATIONS_MAIN_COLUMNS, RELATIONS_MAIN_LIST, }; use crate::bindings::_helpers::planner_warehouse::cursors::{CursorInputs, cursor_snapshots}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::IntoPyObject; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::pyfunction; @@ -80,11 +83,10 @@ fn relevant_state_keys<'py>( if selected.is_none() { return Ok(selected.clone()); } - let deps = - imported(py, "sqlbuild._native", "build_execution_upstream_deps")?.call1((project,))?; + let deps = registry::native(py, NativeAttr::BuildExecutionUpstreamDeps)?.call1((project,))?; native( py, - "transitive_closure_many", + NativeAttr::TransitiveClosureMany, &[ ("starts", selected.clone()), ("edges", deps), @@ -205,7 +207,7 @@ fn metadata_name_filter( } let intermediate = native( py, - "build_upstream_intermediate_source_map", + NativeAttr::BuildUpstreamIntermediateSourceMap, &[ ("project", project.clone()), ("selected_keys", selected.clone()), @@ -234,7 +236,7 @@ fn active_catalog<'py>( connection: &Bound<'py, PyAny>, ) -> PyResult> { call_with( - &imported(adapter.py(), ACTIVE_CATALOG.0, ACTIVE_CATALOG.1)?, + &host::entry(adapter.py(), ACTIVE_CATALOG)?, &[ ("adapter", adapter.clone()), ("connection", connection.clone()), @@ -300,7 +302,7 @@ pub(crate) fn list_relations<'py>( names: Bound<'py, PyAny>, ) -> PyResult> { call_with( - &imported(adapter.py(), RELATIONS_MAIN_LIST.0, RELATIONS_MAIN_LIST.1)?, + &host::entry(adapter.py(), RELATIONS_MAIN_LIST)?, &[ ("adapter", adapter.clone()), ("connection", connection.clone()), @@ -317,11 +319,7 @@ pub(crate) fn columns_for<'py>( relations: Bound<'py, PyAny>, ) -> PyResult> { call_with( - &imported( - adapter.py(), - RELATIONS_MAIN_COLUMNS.0, - RELATIONS_MAIN_COLUMNS.1, - )?, + &host::entry(adapter.py(), RELATIONS_MAIN_COLUMNS)?, &[ ("adapter", adapter.clone()), ("connection", connection.clone()), @@ -486,7 +484,7 @@ fn gather_fingerprints<'py>( ) -> PyResult> { let py = request.project.py(); let Some(schemas) = schemas else { - return native(py, "WarehouseFingerprints", &[]); + return native(py, RecordId::WarehouseFingerprints, &[]); }; let node_names: Option> = if relevant.is_none() { None @@ -521,7 +519,7 @@ fn gather_fingerprints<'py>( let schema_lower = lower(&text(py, schema))?; let mut set = native( py, - "read_latest_fingerprints", + NativeAttr::ReadLatestFingerprints, &[ ("connection", request.connection.clone()), ("execute", request.execute.clone()), @@ -570,7 +568,7 @@ fn gather_fingerprints<'py>( if node_names.is_some() { set = native( py, - "select_latest_fingerprints", + NativeAttr::SelectLatestFingerprints, &[ ("fingerprint_set", set), ("node_names", node_names_value.clone()), @@ -605,7 +603,7 @@ fn gather_fingerprints<'py>( } native( py, - "WarehouseFingerprints", + RecordId::WarehouseFingerprints, &[ ("models", models.into_any()), ("functions", functions.into_any()), @@ -636,7 +634,7 @@ pub(crate) fn warehouse_snapshot<'py>( let schemas = target_schemas(project, &relevant)?; let metadata_names = metadata_name_filter(project, &relevant)?; if schemas.is_empty() && metadata_names.is_none() { - return native(py, "WarehouseSnapshot", &[]); + return native(py, RecordId::WarehouseSnapshot, &[]); } let query_schemas = (!schemas.is_empty()).then_some(&schemas); prefetch_schemas(request, &database, &schemas, &relevant)?; @@ -692,7 +690,7 @@ pub(crate) fn warehouse_snapshot<'py>( }; native( py, - "WarehouseSnapshot", + RecordId::WarehouseSnapshot, &[ ("existing_relations", relations.into_any()), ("existing_columns", columns.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_columns.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_columns.rs index 36f4cdcce9..7b3260e026 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_columns.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_columns.rs @@ -1,6 +1,7 @@ //! Warehouse source relation listing and column gathering for planning. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_phases::objects::{frozenset, text, tuple}; use crate::bindings::_helpers::planner_warehouse::constants::{ COLUMN_INFO, METADATA_NAME_FILTER_LIMIT, RELATION_LOOKUP, @@ -84,7 +85,7 @@ pub(crate) fn source_inspection<'py>( let mut listed: Vec> = Vec::new(); let mut listed_names: HashSet = HashSet::new(); let mut groups: Vec<(String, BTreeSet)> = Vec::new(); - let column_info = imported(py, COLUMN_INFO.0, COLUMN_INFO.1)?; + let column_info = host::entry(py, COLUMN_INFO)?; for entry in &entries { let expression = entry.getattr("expression")?; if !expression.is_none() { @@ -120,7 +121,7 @@ pub(crate) fn source_inspection<'py>( None => groups.push((key, BTreeSet::from([schema]))), } } - let lookup = imported(py, RELATION_LOOKUP.0, RELATION_LOOKUP.1)?.getattr("key")?; + let lookup = host::entry(py, RELATION_LOOKUP)?.getattr("key")?; for (key, schemas) in groups { let database = (!key.is_empty()).then_some(key.as_str()); let database_value = match database { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_deferral.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_deferral.rs index be0af28693..d31120fc7b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_deferral.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_deferral.rs @@ -1,13 +1,16 @@ //! Source read deferral for managed source loaders. use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; -use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; use crate::bindings::_helpers::planner_phases::objects::{ native, optional_text, planner_error, replace, text, }; use crate::bindings::_helpers::planner_warehouse::constants::{ MODEL_HOOK_CONFIG_KEYS, SQL_HOOK_ENTRY, }; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::pyfunction; use pyo3::types::PyDict as KeywordDict; @@ -43,9 +46,8 @@ fn hook_source_reads( ) -> PyResult> { let mut names: Vec = Vec::new(); let py = model.py(); - let hook_entry = imported(py, SQL_HOOK_ENTRY.0, SQL_HOOK_ENTRY.1)?; - let source_kind = - imported(py, "sqlbuild.python_nodes.types", "SqlResourceRefKind")?.getattr("SOURCE")?; + let hook_entry = registry::class(py, SQL_HOOK_ENTRY)?; + let source_kind = host::entry(py, UserCodeHost::SqlResourceRefKind)?.getattr("SOURCE")?; let values = model.getattr("config")?.getattr("values")?; for key in MODEL_HOOK_CONFIG_KEYS { let value = values.call_method1("get", (key,))?; @@ -127,7 +129,7 @@ fn resolve_target<'py>( ) -> PyResult> { native( project_config.py(), - "resolve_target_config", + NativeAttr::ResolveTargetConfig, &[ ("project_config", project_config.clone()), ("local_config", local_config.clone()), @@ -191,7 +193,7 @@ fn target_field<'py>( Ok(Some( native( py, - "render_project_var_text", + NativeAttr::RenderProjectVarText, &[ ("value", effective_vars.get_item(name)?), ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_freshness.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_freshness.rs index fcc5658781..6fa37b875c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_freshness.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_freshness.rs @@ -5,11 +5,14 @@ use crate::bindings::_helpers::compile_driver::python_calls::{call_with, importe use crate::bindings::_helpers::freshness::planning::{ build_direct_source_freshness_planning_result, build_direct_source_freshness_propagation_result, }; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_phases::objects::{ model_key_names, native, replace, text, tuple, }; use crate::bindings::_helpers::planner_warehouse::constants::RUN_SCOPE; use crate::bindings::_helpers::planner_warehouse::snapshot::{destination_nodes, lower}; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::types::PyDict as KeywordDict; use pyo3::types::{PyBool, PyDict, PyDictMethods}; @@ -47,12 +50,12 @@ pub(crate) fn source_freshness_unknown_warnings<'py>( if let Some(message) = freshness_unknown_message(&reason, &names) { warnings.push(native( py, - "PlanWarning", + RecordId::PlanWarning, &[ ("model_name", py.None().into_bound(py)), ( "severity", - imported(py, "sqlbuild._native", "WarningSeverity")?.getattr("WARNING")?, + registry::class(py, RecordId::WarningSeverity)?.getattr("WARNING")?, ), ("message", text(py, &message)), ], @@ -148,7 +151,7 @@ pub(crate) fn build_planner_source_freshness_result<'py>( let observed_at = imported(py, "datetime", "datetime")? .call_method1("now", (imported(py, "datetime", "UTC")?,))?; let result = within( - &imported(py, RUN_SCOPE.0, RUN_SCOPE.1)?.call1((project.getattr("run_id")?,))?, + &host::entry(py, RUN_SCOPE)?.call1((project.getattr("run_id")?,))?, |_| { call_with( wrap_pyfunction!(build_direct_source_freshness_planning_result, py)?.as_any(), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_tables.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_tables.rs index 60dcbb90af..ea54c1ccd9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_tables.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/source_tables.rs @@ -2,11 +2,13 @@ use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_phases::objects::{planner_error, tuple}; use crate::bindings::_helpers::planner_warehouse::constants::{ MISSING_SOURCE_TABLE_CODE, RENDER_SOURCE_READ, RUN_BOUNDED, WILDCARD_TABLE_CHARACTER, YAML_ENTRY_LINE, }; +use crate::bindings::_helpers::records::registry; use pyo3::IntoPyObject; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::pyfunction; @@ -65,7 +67,7 @@ fn read_relation<'py>( entry: &Bound<'py, PyAny>, ) -> PyResult> { call_with( - &imported(adapter.py(), RENDER_SOURCE_READ.0, RENDER_SOURCE_READ.1)?, + ®istry::native(adapter.py(), RENDER_SOURCE_READ)?, &[ ("adapter", adapter.clone()), ("source_entry", entry.clone()), @@ -80,7 +82,7 @@ fn declaration_location(source: &Bound<'_, PyAny>) -> PyResult { .call_method0("as_posix")? .extract()?; let line = call_with( - &imported(source.py(), YAML_ENTRY_LINE.0, YAML_ENTRY_LINE.1)?, + ®istry::native(source.py(), YAML_ENTRY_LINE)?, &[ ("contents", file.getattr("contents")?), ("name", source.getattr("name")?), @@ -142,7 +144,7 @@ pub(crate) fn check_selected_source_tables_exist<'py>( .call1((adapter.getattr("metadata_inspection_concurrency")?,))? .extract()?; let probes = call_with( - &imported(py, RUN_BOUNDED.0, RUN_BOUNDED.1)?, + &host::entry(py, RUN_BOUNDED)?, &[ ("tasks", tuple(py, tasks)?), ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/watermarks.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/watermarks.rs index 42ab6ce201..ab046e7c97 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/watermarks.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/watermarks.rs @@ -4,6 +4,8 @@ use crate::bindings::_helpers::planner_phases::objects::{config_str, planner_err use crate::bindings::_helpers::planner_warehouse::constants::{ CURSOR_TYPE_INTEGER, CURSOR_TYPE_TIMESTAMP, WATERMARK_CONTRACT_CODE, }; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult}; use pyo3::pyfunction; use pyo3::types::{PyDict, PyDictMethods}; @@ -21,12 +23,8 @@ pub(crate) fn watermark_type_is_compatible( else { return Ok(true); }; - let classified = crate::bindings::_helpers::compile_driver::python_calls::imported( - declared_type.py(), - "sqlbuild._native", - "classify_cursor_sql_type", - )? - .call1((declared_type,))?; + let classified = registry::native(declared_type.py(), NativeAttr::ClassifyCursorSqlType)? + .call1((declared_type,))?; classified.eq(expected) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/project_assembly/native_resources.rs b/crates/sqlbuild-python/src/bindings/_helpers/project_assembly/native_resources.rs index a4cb860f93..6df67839b2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/project_assembly/native_resources.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/project_assembly/native_resources.rs @@ -23,9 +23,10 @@ use crate::bindings::_helpers::model_config::template_data::record_reads; use crate::bindings::_helpers::project_assembly::models::{ NativeProjectResources, NativeResourceFailure, }; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; use crate::bindings::types::CompilerDetach; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; const OS_MODULE: &str = "os"; const PROJECT_ASSEMBLY_CONTEXT: &str = "native project assembly"; const GENERIC_DIALECT: &str = "generic"; @@ -344,7 +345,7 @@ fn failure_error<'py>( ) -> PyResult> { match error { AssemblyError::CompileInput { message, help } => { - imported(py, COMPILE_ERRORS_MODULE, "CompileInputError")?.call( + registry::error(py, ErrorId::CompileInput)?.call( (message,), Some(&keywords( py, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/project_assembly/syntax_validation.rs b/crates/sqlbuild-python/src/bindings/_helpers/project_assembly/syntax_validation.rs index 28fb19a8fc..72415f7c9e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/project_assembly/syntax_validation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/project_assembly/syntax_validation.rs @@ -11,12 +11,9 @@ use sqlbuild_analysis::assembly::project::main::sql_analysis_opt_out_help::sql_a use sqlbuild_analysis::assembly::project::models::{SyntaxCheck, SyntaxMode}; use crate::bindings::_helpers::project_assembly::resources::syntax_error_message; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; -const DISCOVERY_MODELS_MODULE: &str = "sqlbuild._native"; -const SQL_HOOK_ENTRY: &str = "SqlHookEntry"; -const PYTHON_HOOK_ENTRY: &str = "PythonHookEntry"; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; -const COMPILE_INPUT_ERROR: &str = "CompileInputError"; const GENERIC_DIALECT: &str = "generic"; /// `(placeholder defaults, dialect)` a statement parses with. @@ -44,10 +41,8 @@ fn syntax_error(py: Python<'_>, message: String) -> PyErr { let raised = || -> PyResult { let keywords: Bound<'_, PyDict> = PyDict::new(py); keywords.set_item("help", sql_analysis_opt_out_help(true))?; - let error: Bound<'_, PyAny> = py - .import(COMPILE_ERRORS_MODULE)? - .getattr(COMPILE_INPUT_ERROR)? - .call((message,), Some(&keywords))?; + let error: Bound<'_, PyAny> = + registry::error(py, ErrorId::CompileInput)?.call((message,), Some(&keywords))?; Ok(PyErr::from_value(error)) }; raised().unwrap_or_else(|error| error) @@ -158,7 +153,6 @@ fn validate_hook_sql_syntax( let (model_name, file_path) = model; let (placeholders, dialect) = parsing; let py: Python<'_> = value.py(); - let models: Bound<'_, PyModule> = py.import(DISCOVERY_MODELS_MODULE)?; let dialect: String = dialect .filter(|name| !name.is_empty()) .unwrap_or_else(|| GENERIC_DIALECT.to_owned()); @@ -167,8 +161,8 @@ fn validate_hook_sql_syntax( model_name: &model_name, placeholders: &placeholders, dialect: &dialect, - sql_hook_entry: models.getattr(SQL_HOOK_ENTRY)?, - python_hook_entry: models.getattr(PYTHON_HOOK_ENTRY)?, + sql_hook_entry: registry::class(py, RecordId::SqlHookEntry)?, + python_hook_entry: registry::class(py, RecordId::PythonHookEntry)?, }; check_hook_value( &context, @@ -234,10 +228,7 @@ pub(crate) fn hook_statements( /// The hook SQL strings a hook value holds, in validation order. #[pyfunction] pub(crate) fn hook_sql_statements(value: Bound<'_, PyAny>) -> PyResult> { - let sql_hook_entry: Bound<'_, PyAny> = value - .py() - .import(DISCOVERY_MODELS_MODULE)? - .getattr(SQL_HOOK_ENTRY)?; + let sql_hook_entry: Bound<'_, PyAny> = registry::class(value.py(), RecordId::SqlHookEntry)?; hook_statements(&value, &sql_hook_entry) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/query_command/dag.rs b/crates/sqlbuild-python/src/bindings/_helpers/query_command/dag.rs index 73b24b16fd..45ccf21c8d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/query_command/dag.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/query_command/dag.rs @@ -6,14 +6,11 @@ use pyo3::types::{PyBool, PyDict}; use pyo3::{pyfunction, wrap_pyfunction}; use sqlbuild_core::query_command::main::render_dag_summary::render_dag_summary; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::graph::python_node_graph::python_node_graph; use crate::bindings::_helpers::query_command::project::{ProjectAdapter, project_adapter}; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const EXTERNAL_REFS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; fn array_length(document: &serde_json::Value, key: &str) -> PyResult { document @@ -46,18 +43,14 @@ fn run_dag_command<'py>(py: Python<'py>, options: Option>) -> adapter, } = project_adapter(py, project_dir)?; let resolver: Bound<'py, PyAny> = call_with( - &imported( - py, - EXTERNAL_REFS_MODULE, - "resolve_external_sql_reference_resolver", - )?, + ®istry::native(py, NativeAttr::ResolveExternalSqlReferenceResolver)?, &[ ("project_dir", project_dir), ("discovered_inputs", discovered.clone()), ], )?; let graph: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "build_project_graph")?, + ®istry::native(py, NativeAttr::BuildProjectGraph)?, &[ ("discovered_inputs", discovered.clone()), ("adapter", adapter), @@ -71,7 +64,7 @@ fn run_dag_command<'py>(py: Python<'py>, options: Option>) -> )?; let python_graph: Bound<'py, PyAny> = python_node_graph(&discovered)?; let dag_json: String = call_with( - &imported(py, NATIVE_MODULE, "build_dag_json")?, + ®istry::native(py, NativeAttr::BuildDagJson)?, &[ ("graph", graph), ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/query_command/project.rs b/crates/sqlbuild-python/src/bindings/_helpers/query_command/project.rs index 2368f1e8c6..11fe3cdf69 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/query_command/project.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/query_command/project.rs @@ -4,9 +4,8 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use crate::bindings::_helpers::command_runtime::adapters::resolve_cli_adapter; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; /// The project directory, defaulting to the working directory, with its inputs and adapter. pub(crate) struct ProjectAdapter<'py> { @@ -24,11 +23,11 @@ pub(crate) fn project_adapter<'py>( None => imported(py, "pathlib", "Path")?.call_method0("cwd")?, }; let discovered: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[("project_dir", project_dir.clone())], )?; let adapter_name: Bound<'py, PyAny> = call_with( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/query_command/query.rs b/crates/sqlbuild-python/src/bindings/_helpers/query_command/query.rs index f8eb8af746..698ba6b7d9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/query_command/query.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/query_command/query.rs @@ -13,17 +13,16 @@ use sqlbuild_core::query_command::main::render_query_long::render_query_long; use sqlbuild_core::query_command::main::render_query_table::render_query_table; use sqlbuild_core::query_command::models::QueryDisplay; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::query_command::project::{ProjectAdapter, project_adapter}; use crate::bindings::_helpers::query_command::query_values::{ csv_text, display_text, json_cell, python_json_rows, strict_width, }; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ERRORS_MODULE: &str = "sqlbuild._native"; -const ADAPTER_ERRORS_MODULE: &str = "sqlbuild.adapter.contract.exceptions"; const LONG_FORMAT: &str = "long"; const TABLE_FORMAT: &str = "table"; const JSON_FORMAT: &str = "json"; @@ -41,7 +40,7 @@ fn user_error(py: Python<'_>, message: &str, code: Option<&str>, help: Option<&s if let Some(help) = help { options.set_item("help", help)?; } - imported(py, ERRORS_MODULE, "CliUserError")?.call((message,), Some(&options)) + registry::error(py, ErrorId::CliUser)?.call((message,), Some(&options)) })(); match raised { Ok(error) => PyErr::from_value(error), @@ -147,7 +146,7 @@ fn run_sql<'py>( ) -> PyResult> { let py: Python<'py> = sql.py(); let connection_config: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "resolve_project_connection_config")?, + ®istry::native(py, NativeAttr::ResolveProjectConnectionConfig)?, &[ ("discovered_inputs", project.discovered.clone()), ("project_dir", project.project_dir.clone()), @@ -173,7 +172,7 @@ fn run_sql<'py>( ], ); let queried: PyResult> = queried.map_err(|error| { - let adapter_error: bool = imported(py, ADAPTER_ERRORS_MODULE, "AdapterUserError") + let adapter_error: bool = host::entry(py, AdapterHost::AdapterUserError) .is_ok_and(|class| error.value(py).is_instance(&class).unwrap_or(false)); if adapter_error || !error.is_instance_of::(py) { return error; @@ -183,9 +182,9 @@ fn run_sql<'py>( Ok(message) => message.str().map(Bound::into_any), Err(_) => value.str().map(Bound::into_any), }; - let wrapped: PyErr = match message.and_then(|message| { - imported(py, ERRORS_MODULE, "QueryExecutionError")?.call1((message,)) - }) { + let wrapped: PyErr = match message + .and_then(|message| registry::error(py, ErrorId::QueryExecution)?.call1((message,))) + { Ok(raised) => PyErr::from_value(raised), Err(failure) => failure, }; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/auditing.rs b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/auditing.rs index 737a0b63b1..e963885efc 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/auditing.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/auditing.rs @@ -8,16 +8,12 @@ use sqlbuild_model_config::header_metadata::main::check_threshold_bound::check_t use sqlbuild_model_config::header_metadata::main::check_threshold_pair::check_threshold_pair; use sqlbuild_model_config::header_metadata::models::{ThresholdBound, ThresholdInput}; -use crate::bindings::_helpers::compile_driver::python_calls::imported; -use crate::bindings::_helpers::records::constants::NATIVE_MODULE; - -const MEASUREMENT_AUDIT_ERROR: &str = "MeasurementAuditError"; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; /// The `MeasurementAuditError` carrying `message`. fn measurement_error(py: Python<'_>, message: &str) -> PyErr { - match imported(py, NATIVE_MODULE, MEASUREMENT_AUDIT_ERROR) - .and_then(|class| class.call1((message,))) - { + match registry::error(py, ErrorId::MeasurementAudit).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/compiled.rs b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/compiled.rs index d04e1d558e..2bb74087d9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/compiled.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/compiled.rs @@ -5,7 +5,8 @@ use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyErr, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyList, PyListMethods, PyTuple}; use crate::bindings::_helpers::compile_driver::python_calls::imported; -use crate::bindings::_helpers::records::constants::NATIVE_MODULE; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::relation_keys::entry_relation_key_row; /// Module of the compiled resource type enum. @@ -41,6 +42,13 @@ fn coerce(py: Python<'_>, value: &Py, (module, class): (&str, &str)) -> P .unbind()) } +/// The native string enum `class(value)`. +fn coerce_native(py: Python<'_>, value: &Py, class: RecordId) -> PyResult> { + Ok(registry::class(py, class)? + .call1((value.bind(py),))? + .unbind()) +} + /// `value` coerced to `CompiledResourceType` unless it is `None`. fn coerce_resource_type(py: Python<'_>, value: &Py) -> PyResult> { if value.is_none(py) { @@ -54,15 +62,15 @@ pub(crate) fn finish_compiler_diagnostic( py: Python<'_>, mut values: Vec>, ) -> PyResult>> { - values[DIAGNOSTIC_PHASE_FIELD] = coerce( + values[DIAGNOSTIC_PHASE_FIELD] = coerce_native( py, &values[DIAGNOSTIC_PHASE_FIELD], - (NATIVE_MODULE, "DiagnosticPhase"), + RecordId::DiagnosticPhase, )?; - values[DIAGNOSTIC_SEVERITY_FIELD] = coerce( + values[DIAGNOSTIC_SEVERITY_FIELD] = coerce_native( py, &values[DIAGNOSTIC_SEVERITY_FIELD], - (NATIVE_MODULE, "DiagnosticSeverity"), + RecordId::DiagnosticSeverity, )?; values[DIAGNOSTIC_RESOURCE_TYPE_FIELD] = coerce_resource_type(py, &values[DIAGNOSTIC_RESOURCE_TYPE_FIELD])?; @@ -79,7 +87,7 @@ pub(crate) fn finish_compiler_diagnostic( keywords.set_item("line", values[DIAGNOSTIC_LINE_FIELD].bind(py))?; keywords.set_item("column", values[DIAGNOSTIC_COLUMN_FIELD].bind(py))?; let location: Bound<'_, PyAny> = - imported(py, NATIVE_MODULE, "SourceLocation")?.call((), Some(&keywords))?; + registry::class(py, RecordId::SourceLocation)?.call((), Some(&keywords))?; values[DIAGNOSTIC_LOCATION_FIELD] = location.unbind(); } let location: Bound<'_, PyAny> = values[DIAGNOSTIC_LOCATION_FIELD].bind(py).clone(); @@ -95,7 +103,7 @@ pub(crate) fn finish_compiler_diagnostic( pub(crate) fn diagnostic_is_error(instance: &Bound<'_, PyAny>) -> PyResult> { let py: Python<'_> = instance.py(); let error: Bound<'_, PyAny> = - imported(py, NATIVE_MODULE, "DiagnosticSeverity")?.getattr("ERROR")?; + registry::class(py, RecordId::DiagnosticSeverity)?.getattr("ERROR")?; instance.getattr("severity")?.eq(error)?.into_py_any(py) } @@ -104,7 +112,7 @@ pub(crate) fn finish_sql_reference( py: Python<'_>, mut values: Vec>, ) -> PyResult>> { - values[0] = coerce(py, &values[0], (NATIVE_MODULE, "SqlReferenceKind"))?; + values[0] = coerce_native(py, &values[0], RecordId::SqlReferenceKind)?; Ok(values) } @@ -362,7 +370,7 @@ pub(crate) fn build_context_declaration_expansion( let keywords: Bound<'_, PyDict> = PyDict::new(py); keywords.set_item( "declarations", - imported(py, NATIVE_MODULE, "DeclarationResolutionContext")? + registry::class(py, RecordId::DeclarationResolutionContext)? .call((), Some(&declarations))?, )?; keywords.set_item("value_renderer", instance.getattr("value_renderer")?)?; @@ -371,7 +379,7 @@ pub(crate) fn build_context_declaration_expansion( instance.getattr("collection_rendering")?, )?; keywords.set_item("resolver", instance.getattr("declaration_resolver")?)?; - Ok(imported(py, NATIVE_MODULE, "DeclarationExpansionContext")? + Ok(registry::class(py, RecordId::DeclarationExpansionContext)? .call((), Some(&keywords))? .unbind()) } @@ -379,6 +387,6 @@ pub(crate) fn build_context_declaration_expansion( /// A `CompileInputError` carrying `message`. pub(crate) fn compile_input_error(py: Python<'_>, message: &str) -> PyResult { Ok(PyErr::from_value( - imported(py, NATIVE_MODULE, "CompileInputError")?.call1((message,))?, + registry::error(py, ErrorId::CompileInput)?.call1((message,))?, )) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/cursors.rs b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/cursors.rs index 9fc69a029e..803d2a8ebd 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/cursors.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/cursors.rs @@ -5,14 +5,19 @@ use pyo3::types::{PyString, PyTuple}; use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::cursor::scalars::parse_value; -use crate::bindings::_helpers::records::constants::NATIVE_MODULE; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; /// One cursor value conversion. type Conversion = for<'py> fn(&Bound<'py, PyAny>) -> PyResult>; -const SCALAR_CLASSES: [&str; 3] = ["TimestampValue", "DateValue", "IntegerValue"]; -const CURSOR_TYPES_MODULE: &str = "sqlbuild.cursor_algebra.types"; -const BOUND_SENTINEL: &str = "BoundSentinel"; +const SCALAR_CLASSES: [RecordId; 3] = [ + RecordId::TimestampValue, + RecordId::DateValue, + RecordId::IntegerValue, +]; const CURSOR_ERRORS_MODULE: &str = "sqlbuild.cursor_algebra.exceptions"; const CURSOR_ALGEBRA_ERROR: &str = "CursorAlgebraError"; @@ -20,7 +25,7 @@ const CURSOR_ALGEBRA_ERROR: &str = "CursorAlgebraError"; pub(crate) fn inferred<'py>(raw: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = raw.py(); for class in SCALAR_CLASSES { - if raw.is_instance(&imported(py, NATIVE_MODULE, class)?)? { + if raw.is_instance(®istry::class(py, class)?)? { return Ok(raw.clone()); } } @@ -49,7 +54,7 @@ pub(crate) fn optional_inferred<'py>(raw: &Bound<'py, PyAny>) -> PyResult(raw: &Bound<'py, PyAny>) -> PyResult> { - let sentinel: Bound<'py, PyAny> = imported(raw.py(), CURSOR_TYPES_MODULE, BOUND_SENTINEL)?; + let sentinel: Bound<'py, PyAny> = host::entry(raw.py(), ExecutorHost::BoundSentinel)?; if raw.is_instance(&sentinel)? { return Ok(raw.clone()); } @@ -115,7 +120,7 @@ pub(crate) fn finish_cursor_snapshot( .collect::>()?; values[13] = PyTuple::new(py, pairs)?.into_any().unbind(); let mode: Bound<'_, PyAny> = - imported(py, NATIVE_MODULE, "CursorWatermarkMode")?.call1((values[10].bind(py),))?; + registry::class(py, RecordId::CursorWatermarkMode)?.call1((values[10].bind(py),))?; values[10] = mode.unbind(); Ok(values) } @@ -172,7 +177,7 @@ pub(crate) fn finish_input_relation( ) -> PyResult>> { if !values[2].bind(py).is_none() { let grain: Bound<'_, PyAny> = - imported(py, NATIVE_MODULE, "CursorGrain")?.call1((values[2].bind(py),))?; + registry::class(py, RecordId::CursorGrain)?.call1((values[2].bind(py),))?; values[2] = grain.unbind(); } converted(py, values, (&[5, 6], optional_inferred)) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/freshness.rs b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/freshness.rs index 7264226e08..0fdfe4b00e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/freshness.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/freshness.rs @@ -3,9 +3,9 @@ use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::PyDict; -use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::freshness::observation::require_aware_utc_datetime; -use crate::bindings::_helpers::records::constants::NATIVE_MODULE; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; /// `SourceFreshnessRecord.observed_at` position. const OBSERVED_AT_FIELD: usize = 9; @@ -34,7 +34,7 @@ pub(crate) fn freshness_record_identity(instance: &Bound<'_, PyAny>) -> PyResult for name in IDENTITY_FIELDS { pyo3::types::PyDictMethods::set_item(&keywords, name, instance.getattr(name)?)?; } - Ok(imported(py, NATIVE_MODULE, "SourceFreshnessIdentity")? + Ok(registry::class(py, RecordId::SourceFreshnessIdentity)? .call((), Some(&keywords))? .unbind()) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/lexical.rs b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/lexical.rs index dd5e7855fb..1160ad05c2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/lexical.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/lexical.rs @@ -10,8 +10,8 @@ use sqlbuild_sqltext::sql_scan::main::reads_differently_from_generic::reads_diff use sqlbuild_sqltext::sql_scan::main::unsupported_line_comment_prefixes::unsupported_line_comment_prefixes; use sqlbuild_sqltext::sql_scan::models::LexicalSyntax; -use crate::bindings::_helpers::compile_driver::python_calls::imported; -use crate::bindings::_helpers::records::constants::NATIVE_MODULE; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; /// `SqlLexicalSyntax.line_comment_prefixes` position. const LINE_COMMENT_PREFIXES_FIELD: usize = 5; @@ -56,7 +56,7 @@ pub(crate) fn finish_lexical_syntax( PyList::new(py, &supported)?.repr()? ); let error: Bound<'_, PyAny> = - imported(py, NATIVE_MODULE, "UnsupportedLexicalSyntaxError")?.call1((message,))?; + registry::error(py, ErrorId::UnsupportedLexicalSyntax)?.call1((message,))?; Err(PyErr::from_value(error)) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/lineage_facts.rs b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/lineage_facts.rs index 21d5d48e4a..144ea2cdb4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/lineage_facts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/lineage_facts.rs @@ -5,9 +5,9 @@ use pyo3::exceptions::PyIndexError; use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PySlice, PyTuple, PyTupleMethods}; -use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::record_behaviours::call_arguments::call_argument; -use crate::bindings::_helpers::records::constants::NATIVE_MODULE; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; /// `ColumnTransformKind` members by native transform code. const TRANSFORM_KINDS: [&str; 6] = [ @@ -23,26 +23,24 @@ const CONFIDENCES: [&str; 3] = ["UNKNOWN", "HIGH", "MEDIUM"]; /// The `ColumnTransformKind` member of a native transform code. pub(crate) fn transform_kind_member(py: Python<'_>, code: usize) -> PyResult> { - coded_member(py, ("ColumnTransformKind", &TRANSFORM_KINDS), code) + coded_member(py, (RecordId::ColumnTransformKind, &TRANSFORM_KINDS), code) } /// The `ColumnLineageConfidence` member of a native confidence code. pub(crate) fn confidence_member(py: Python<'_>, code: usize) -> PyResult> { - coded_member(py, ("ColumnLineageConfidence", &CONFIDENCES), code) + coded_member(py, (RecordId::ColumnLineageConfidence, &CONFIDENCES), code) } /// The enum member at `code` of `members`. fn coded_member( py: Python<'_>, - (class, members): (&str, &[&str]), + (class, members): (RecordId, &[&str]), code: usize, ) -> PyResult> { let Some(member) = members.get(code) else { return Err(PyIndexError::new_err("tuple index out of range")); }; - Ok(imported(py, NATIVE_MODULE, class)? - .getattr(*member)? - .unbind()) + Ok(registry::member(py, class, member)?.unbind()) } /// `CompactLineageFacts.resource_name(index)`: the model's name for a native relation index. @@ -81,7 +79,7 @@ fn project_row(facts: &Bound<'_, PyAny>, row: &Bound<'_, PyAny>) -> PyResult, ) = row.extract()?; - let source_class: Bound<'_, PyAny> = imported(py, NATIVE_MODULE, "CompiledLineageSourceFact")?; + let source_class: Bound<'_, PyAny> = registry::class(py, RecordId::CompiledLineageSourceFact)?; let mut upstream: Vec> = Vec::new(); for source in sources.try_iter()? { let source: Bound<'_, PyTuple> = source?.cast_into::()?; @@ -98,7 +96,7 @@ fn project_row(facts: &Bound<'_, PyAny>, row: &Bound<'_, PyAny>) -> PyResult, row: &Bound<'_, PyAny>) -> PyResult) -> PyResult { member.str()?.extract() @@ -194,7 +192,7 @@ pub(crate) fn history_status( } else { "PENDING_ARCHIVE" }; - Ok(imported(py, NATIVE_MODULE, VIEW_STATUS)? + Ok(registry::class(py, RecordId::OldNameViewStatus)? .getattr(status)? .unbind()) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/planner.rs b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/planner.rs index 8c6fbbaffd..270d52fef3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/planner.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/planner.rs @@ -12,7 +12,8 @@ use sqlbuild_core::migration_values::models::{DecisionTraits, ModelMigrationFact use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::cursor::scalars::parse_value; use crate::bindings::_helpers::record_behaviours::call_arguments::call_argument; -use crate::bindings::_helpers::records::constants::NATIVE_MODULE; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; const DECREASING_DIRECTIONS: [&str; 2] = ["decrease", "mixed"]; const STEP_ACTIONS: [&str; 2] = ["archive_and_view", "view_only"]; @@ -22,7 +23,6 @@ const SOURCE_MISSING: &str = "source_missing"; const DENY_POLICY: &str = "deny"; const INCOMPATIBLE: &str = "incompatible"; const DEFAULT_TARGET_LABEL: &str = "default"; -const PLANNER_INPUT_ERROR: &str = "PlannerInputError"; const INTEGER_CURSOR: &str = "integer"; /// `CursorOverrides` field positions: `start_ts`, `end_ts`, `start_int`, `end_int`, `start`, `end`. const OVERRIDE_FIELDS: [&str; 6] = ["start_ts", "end_ts", "start_int", "end_int", "start", "end"]; @@ -153,8 +153,7 @@ pub(crate) fn column_migration_blocks_build(entry: &Bound<'_, PyAny>) -> PyResul /// The `PlannerInputError` carrying `message`. fn planner_error(py: Python<'_>, message: String) -> PyErr { - match imported(py, NATIVE_MODULE, PLANNER_INPUT_ERROR).and_then(|class| class.call1((message,))) - { + match registry::error(py, ErrorId::PlannerInput).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } @@ -211,7 +210,7 @@ fn override_integer<'py>( } let parsed: Bound<'py, PyAny> = parse_value(value, &PyString::new(py, INTEGER_CURSOR).into_any())?; - if parsed.is_instance(&imported(py, NATIVE_MODULE, "IntegerValue")?)? { + if parsed.is_instance(®istry::class(py, RecordId::IntegerValue)?)? { return Ok(parsed); } Err(invalid()) @@ -305,9 +304,9 @@ pub(crate) fn forward_only_backfill(py: Python<'_>) -> PyResult let keywords: Bound<'_, PyDict> = PyDict::new(py); keywords.set_item( "action", - imported(py, NATIVE_MODULE, "BackfillAction")?.getattr("FORWARD_ONLY")?, + registry::class(py, RecordId::BackfillAction)?.getattr("FORWARD_ONLY")?, )?; - imported(py, NATIVE_MODULE, "BackfillResult")?.call((), Some(&keywords)) + registry::class(py, RecordId::BackfillResult)?.call((), Some(&keywords)) } /// `ScenarioArtifactIdentity.kind` coerced to `ScenarioArtifactKind`. @@ -316,7 +315,7 @@ pub(crate) fn finish_scenario_artifact_kind( mut values: Vec>, ) -> PyResult>> { if let Some(kind) = values.first_mut() { - *kind = imported(py, NATIVE_MODULE, "ScenarioArtifactKind")? + *kind = registry::class(py, RecordId::ScenarioArtifactKind)? .call1((kind.bind(py),))? .unbind(); } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/python_nodes.rs b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/python_nodes.rs index 24d78cf850..f0c4c41ace 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/python_nodes.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/python_nodes.rs @@ -1,11 +1,12 @@ //! Graph walks over the natively hosted Python-node graph record. -use std::collections::HashSet; - use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyResult}; use pyo3::types::{ PyDict, PyDictMethods, PyFrozenSet, PySet, PySetMethods, PyTuple, PyTupleMethods, }; +use sqlbuild_core::graph_walk::main::breadth_first_reach::breadth_first_reach; + +use crate::bindings::_helpers::graph::node_set::PyNodeSet; /// `PythonNodeGraph.selected_sql_refs(selected_names=...)`: SQL references of the selected closure. pub(crate) fn graph_selected_sql_refs( @@ -25,26 +26,21 @@ pub(crate) fn graph_selected_sql_refs( let nodes_by_name: Bound<'_, PyAny> = graph.getattr("nodes_by_name")?; let upstream: Bound<'_, PyAny> = graph.getattr("upstream_deps")?; let refs: Bound<'_, PySet> = PySet::empty(py)?; - let mut pending: Vec = selected - .try_iter()? - .map(|name| name?.extract::()) - .collect::>()?; - let mut visited: HashSet = HashSet::new(); - while let Some(name) = pending.pop() { - if !visited.insert(name.clone()) { - continue; - } - let node: Bound<'_, PyAny> = nodes_by_name.call_method1("get", (name.as_str(),))?; + let starts: Vec> = selected.try_iter()?.collect::>()?; + let mut visited: PyNodeSet<'_> = PyNodeSet(PySet::new(py, &starts)?); + breadth_first_reach(starts, None, &mut visited, |name| { + upstream + .call_method1("get", (name, PyTuple::empty(py)))? + .try_iter()? + .collect::>>>() + })?; + for name in visited.0.iter() { + let node: Bound<'_, PyAny> = nodes_by_name.call_method1("get", (name,))?; if !node.is_none() { for reference in node.getattr("sql_deps")?.try_iter()? { refs.add(reference?)?; } } - let parents: Bound<'_, PyAny> = - upstream.call_method1("get", (name.as_str(), PyTuple::empty(py)))?; - for parent in parents.try_iter()? { - pending.push(parent?.extract()?); - } } Ok(PyFrozenSet::new(py, refs.iter())?.into_any().unbind()) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/rule_shapes.rs b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/rule_shapes.rs index a535e8be08..901ee0b876 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/rule_shapes.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/rule_shapes.rs @@ -6,7 +6,8 @@ use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyTuple, PyTupleMethods}; use crate::bindings::_helpers::compile_driver::python_calls::imported; -use crate::bindings::_helpers::records::constants::NATIVE_MODULE; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; /// Keyword fields every `RuleOption` constructor requires. const REQUIRED_OPTION_FIELDS: [&str; 3] = ["name", "default", "description"]; @@ -37,7 +38,7 @@ pub(crate) fn project_path_depth(instance: &Bound<'_, PyAny>) -> PyResult) -> PyResult> { let project: Bound<'_, PyAny> = - imported(instance.py(), NATIVE_MODULE, "RuleSubject")?.getattr("PROJECT")?; + registry::class(instance.py(), RecordId::RuleSubject)?.getattr("PROJECT")?; instance .getattr("subject")? .is(&project) @@ -59,7 +60,7 @@ pub(crate) fn rule_option_class_getitem( _kwargs: Option<&Bound<'_, PyDict>>, ) -> PyResult> { let py: Python<'_> = receiver.py(); - let class: Bound<'_, PyAny> = imported(py, NATIVE_MODULE, "RuleOption")?; + let class: Bound<'_, PyAny> = registry::class(py, RecordId::RuleOption)?; Ok(imported(py, "types", "GenericAlias")? .call1((class, args.get_item(0)?))? .unbind()) @@ -100,7 +101,7 @@ fn rule_option( for (name, value) in extra { fields.set_item(*name, value)?; } - Ok(imported(py, NATIVE_MODULE, "RuleOption")? + Ok(registry::class(py, RecordId::RuleOption)? .call((), Some(&fields))? .unbind()) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/records/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/records/constants.rs index 09870bba21..d537996df5 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/records/constants.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/records/constants.rs @@ -6,6 +6,7 @@ use crate::bindings::_helpers::compiler_errors::constants::COMPILER_ERRORS; use crate::bindings::_helpers::compiler_records::constants::COMPILER_RECORDS; use crate::bindings::_helpers::contract_records::constants::CONTRACT_RECORDS; use crate::bindings::_helpers::freshness::observation::normalize_observation; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::planner_records::constants::{ PLANNER_ENUMS, PLANNER_RECORDS, PLANNER_VALUE_RECORDS, }; @@ -13,6 +14,7 @@ use crate::bindings::_helpers::record_behaviours::constants::{ BEHAVIOUR_CACHED_PROPERTIES, BEHAVIOUR_FINISHERS, BEHAVIOUR_METHODS, BEHAVIOUR_ORDERED, BEHAVIOUR_PROPERTIES, BEHAVIOUR_SEQUENCES, }; +use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::records::models::{ ClassValue, EnumSpec, ErrorSpec, FieldDefault, FieldSpec, RecordSpec, }; @@ -283,7 +285,7 @@ const BUILD_INVOCATION: RecordSpec = RecordSpec { FieldSpec::new("effective_target_name", FieldDefault::None), FieldSpec::new( "execution_limits", - FieldDefault::Call("sqlbuild._native", "ExecutionLimitsConfig"), + FieldDefault::NativeCall(RecordId::ExecutionLimitsConfig), ), ], }; @@ -582,7 +584,7 @@ const SCENARIO_TEST_COMMAND_REQUEST: RecordSpec = RecordSpec { FieldSpec::new("refresh", FieldDefault::False), FieldSpec::new( "limit_inputs", - FieldDefault::Call("sqlbuild._native", "ScenarioSnapshotLimitInputs"), + FieldDefault::NativeCall(RecordId::ScenarioSnapshotLimitInputs), ), FieldSpec::new("json_output", FieldDefault::False), FieldSpec::new("json_output_path", FieldDefault::None), @@ -601,7 +603,7 @@ const SCENARIO_CAPTURE_COMMAND_REQUEST: RecordSpec = RecordSpec { FieldSpec::new("retain", FieldDefault::False), FieldSpec::new( "limit_inputs", - FieldDefault::Call("sqlbuild._native", "ScenarioSnapshotLimitInputs"), + FieldDefault::NativeCall(RecordId::ScenarioSnapshotLimitInputs), ), ], }; @@ -760,7 +762,7 @@ const BUILD_COST_FINALIZATION: RecordSpec = RecordSpec { FieldSpec::new("render", FieldDefault::True), FieldSpec::new( "cost_status", - FieldDefault::Member("sqlbuild.cost.types", "CostStatus", "COMPLETE"), + FieldDefault::HostMember(ExecutorHost::CostStatus.id(), "COMPLETE"), ), FieldSpec::new( "cost_message", @@ -799,7 +801,7 @@ const LINEAGE_COMMAND_REQUEST: RecordSpec = RecordSpec { FieldSpec::new("exclude", FieldDefault::EmptyTuple), FieldSpec::new( "lineage_mode", - FieldDefault::Member("sqlbuild._native", "ColumnLineageMode", "RICH"), + FieldDefault::NativeMember(RecordId::ColumnLineageMode, "RICH"), ), FieldSpec::new("include_uses", FieldDefault::False), FieldSpec::new("cli_vars", FieldDefault::None), @@ -869,14 +871,14 @@ const COMPILE_COMMAND_REQUEST: RecordSpec = RecordSpec { FieldSpec::new("no_color", FieldDefault::False), FieldSpec::new( "lineage_mode", - FieldDefault::Member("sqlbuild._native", "CompileLineageMode", "FAST"), + FieldDefault::NativeMember(RecordId::CompileLineageMode, "FAST"), ), FieldSpec::new("select", FieldDefault::EmptyTuple), FieldSpec::new("exclude", FieldDefault::EmptyTuple), FieldSpec::new("cli_vars", FieldDefault::None), FieldSpec::new( "profile_flags", - FieldDefault::Call("sqlbuild._native", "CompileProfileFlags"), + FieldDefault::NativeCall(RecordId::CompileProfileFlags), ), FieldSpec::new("no_cache", FieldDefault::False), FieldSpec::new("debug", FieldDefault::False), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/records/fields.rs b/crates/sqlbuild-python/src/bindings/_helpers/records/fields.rs index 02c32155fd..d37c1c4cda 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/records/fields.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/records/fields.rs @@ -10,10 +10,12 @@ use pyo3::types::{ }; use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::records::constants::{ FROZEN_INSTANCE_ERROR, ORDERED_RECORDS, RECORD_FINISHERS, RECORD_SPEC_ATTRIBUTE, RECORDS, }; use crate::bindings::_helpers::records::models::{FieldDefault, NativeRecord, RecordSpec}; +use crate::bindings::_helpers::records::registry; /// Every registered record spec, in registration order. pub(crate) fn all_specs() -> impl Iterator { @@ -113,7 +115,10 @@ fn default_value<'py>( FieldDefault::EmptyDict => Ok(PyDict::new(py).into_any()), FieldDefault::EmptyFrozenset => Ok(PyFrozenSet::empty(py)?.into_any()), FieldDefault::Member(module, class, member) => imported(py, module, class)?.getattr(member), - FieldDefault::Call(module, name) => imported(py, module, name)?.call0(), + FieldDefault::HostMember(class, member) => host::entry(py, class)?.getattr(member), + FieldDefault::NativeMember(class, member) => registry::class(py, class)?.getattr(member), + FieldDefault::NativeCall(class) => registry::class(py, class)?.call0(), + FieldDefault::NativeClassCall(class) => registry::native(py, class)?.call0(), FieldDefault::Factory(factory) => factory(py), FieldDefault::CallText(module, name, text) => imported(py, module, name)?.call1((text,)), } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/records/ids.rs b/crates/sqlbuild-python/src/bindings/_helpers/records/ids.rs new file mode 100644 index 0000000000..9a8610eb09 --- /dev/null +++ b/crates/sqlbuild-python/src/bindings/_helpers/records/ids.rs @@ -0,0 +1,1313 @@ +//! Typed names of what `sqlbuild._native` registers, so native code never looks them up by string. + +/// An enum of registered names with its declaration-order list and each variant's Python name. +macro_rules! native_ids { + ($(#[$meta:meta])* $vis:vis enum $name:ident { $($variant:ident => $text:literal,)* }) => { + $(#[$meta])* + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + $vis enum $name { + $($variant,)* + } + + impl $name { + /// Every variant, in declaration order. + pub(crate) const ALL: &'static [Self] = &[$(Self::$variant,)*]; + + /// The attribute name the variant is registered under. + pub(crate) const fn name(self) -> &'static str { + match self { + $(Self::$variant => $text,)* + } + } + } + }; +} + +native_ids! { + /// A native record class or string enum, registered from the record and enum specs. + pub(crate) enum RecordId { + AdapterConnectionContext => "AdapterConnectionContext", + AdapterSourceFreshnessBatch => "AdapterSourceFreshnessBatch", + AlignedInterval => "AlignedInterval", + AnalysisCacheContext => "AnalysisCacheContext", + AttachedAuditGateEdge => "AttachedAuditGateEdge", + AuditCommandRequest => "AuditCommandRequest", + AuditGateIdentity => "AuditGateIdentity", + AuditIdentity => "AuditIdentity", + AuditInvocation => "AuditInvocation", + AuditPlanEntry => "AuditPlanEntry", + AuthoredSqlSet => "AuthoredSqlSet", + AuthoredSqlValueCall => "AuthoredSqlValueCall", + AuthoredTimeTravelRetention => "AuthoredTimeTravelRetention", + BackfillAction => "BackfillAction", + BackfillResult => "BackfillResult", + BuildCommandRequest => "BuildCommandRequest", + BuildCostFinalization => "BuildCostFinalization", + BuildInvocation => "BuildInvocation", + ChainStep => "ChainStep", + ChangeDetectionResult => "ChangeDetectionResult", + ChangeKind => "ChangeKind", + CheckCommandRequest => "CheckCommandRequest", + CheckInvocation => "CheckInvocation", + CliCommand => "CliCommand", + CliEntrypointHandlers => "CliEntrypointHandlers", + CloneCommandRequest => "CloneCommandRequest", + ClonePipelineConnection => "ClonePipelineConnection", + ClonePipelineOptions => "ClonePipelineOptions", + ClonePipelineResult => "ClonePipelineResult", + ClonePolicy => "ClonePolicy", + CloneSourcePlanEntry => "CloneSourcePlanEntry", + CollectionRendering => "CollectionRendering", + ColumnContractMode => "ColumnContractMode", + ColumnLineage => "ColumnLineage", + ColumnLineageConfidence => "ColumnLineageConfidence", + ColumnLineageEdge => "ColumnLineageEdge", + ColumnLineageMode => "ColumnLineageMode", + ColumnLineageSource => "ColumnLineageSource", + ColumnMigrationDecision => "ColumnMigrationDecision", + ColumnMigrationEvent => "ColumnMigrationEvent", + ColumnMigrationPlanEntry => "ColumnMigrationPlanEntry", + ColumnMigrationPlanning => "ColumnMigrationPlanning", + ColumnRenameHint => "ColumnRenameHint", + ColumnTransformKind => "ColumnTransformKind", + CommandWarehouseScope => "CommandWarehouseScope", + CompactLineageFacts => "CompactLineageFacts", + CompileAdapterContext => "CompileAdapterContext", + CompileAnalysis => "CompileAnalysis", + CompileAnalysisSelection => "CompileAnalysisSelection", + CompileCommandRequest => "CompileCommandRequest", + CompileDirectLogicSqlTestInputPayload => "CompileDirectLogicSqlTestInputPayload", + CompileFacts => "CompileFacts", + CompileLineageMode => "CompileLineageMode", + CompileModelConfig => "CompileModelConfig", + CompileModelInput => "CompileModelInput", + CompileModelSqlTestInputPayload => "CompileModelSqlTestInputPayload", + CompilePipelineOptions => "CompilePipelineOptions", + CompilePipelineResult => "CompilePipelineResult", + CompileProfileFlags => "CompileProfileFlags", + CompileProjectInputs => "CompileProjectInputs", + CompileSqlReference => "CompileSqlReference", + CompileSqlScenarioCte => "CompileSqlScenarioCte", + CompileSqlScenarioInput => "CompileSqlScenarioInput", + CompileSqlTestCte => "CompileSqlTestCte", + CompileSqlTestInput => "CompileSqlTestInput", + CompiledAudit => "CompiledAudit", + CompiledDirectLogicSqlTestPayload => "CompiledDirectLogicSqlTestPayload", + CompiledFunction => "CompiledFunction", + CompiledLineageColumnFact => "CompiledLineageColumnFact", + CompiledLineageSourceFact => "CompiledLineageSourceFact", + CompiledModel => "CompiledModel", + CompiledModelSqlTestPayload => "CompiledModelSqlTestPayload", + CompiledObjectKey => "CompiledObjectKey", + CompiledProject => "CompiledProject", + CompiledProjectPhaseResult => "CompiledProjectPhaseResult", + CompiledRelationLocation => "CompiledRelationLocation", + CompiledSeed => "CompiledSeed", + CompiledSource => "CompiledSource", + CompiledSqlExpansion => "CompiledSqlExpansion", + CompiledSqlScenario => "CompiledSqlScenario", + CompiledSqlTest => "CompiledSqlTest", + CompiledSqlTestResource => "CompiledSqlTestResource", + CompilerDiagnostic => "CompilerDiagnostic", + CompilerStage => "CompilerStage", + CompletionState => "CompletionState", + ConstantDeclaration => "ConstantDeclaration", + ConstantMetadata => "ConstantMetadata", + ConstantsConfig => "ConstantsConfig", + ContractAdoptionResult => "ContractAdoptionResult", + ContractCommandRequest => "ContractCommandRequest", + ContractEvidence => "ContractEvidence", + ContractFinding => "ContractFinding", + ContractFindingKind => "ContractFindingKind", + ContractPolicy => "ContractPolicy", + ContractValidationResult => "ContractValidationResult", + CostCommandRequest => "CostCommandRequest", + CostConfig => "CostConfig", + CostRunRecord => "CostRunRecord", + CursorBounds => "CursorBounds", + CursorGrain => "CursorGrain", + CursorInputEvidence => "CursorInputEvidence", + CursorInputRelation => "CursorInputRelation", + CursorInputRoles => "CursorInputRoles", + CursorOverridePair => "CursorOverridePair", + CursorOverrides => "CursorOverrides", + CursorSnapshotScope => "CursorSnapshotScope", + CursorType => "CursorType", + CursorWatermarkMode => "CursorWatermarkMode", + CursorsConfig => "CursorsConfig", + CustomHostPartition => "CustomHostPartition", + CustomRuleEvaluation => "CustomRuleEvaluation", + CustomRuleRun => "CustomRuleRun", + CustomRulesOutcome => "CustomRulesOutcome", + DateValue => "DateValue", + DbtCliConfigOverrides => "DbtCliConfigOverrides", + DbtCliOptions => "DbtCliOptions", + DbtCommandExecutionResult => "DbtCommandExecutionResult", + DbtCommandResult => "DbtCommandResult", + DbtConfig => "DbtConfig", + DbtInitCommandRequest => "DbtInitCommandRequest", + DbtInitProgressCallbacks => "DbtInitProgressCallbacks", + DbtInitRequest => "DbtInitRequest", + DbtInitResult => "DbtInitResult", + DbtInteropCommand => "DbtInteropCommand", + DbtInteropCommandArgs => "DbtInteropCommandArgs", + DbtInteropCompiledProject => "DbtInteropCompiledProject", + DbtInteropExecutionRequest => "DbtInteropExecutionRequest", + DbtInteropInvocation => "DbtInteropInvocation", + DbtInteropParsedArgs => "DbtInteropParsedArgs", + DbtInteropPlan => "DbtInteropPlan", + DbtInteropPlanResolution => "DbtInteropPlanResolution", + DbtInteropRoutedArgs => "DbtInteropRoutedArgs", + DbtInteropSelectionResult => "DbtInteropSelectionResult", + DbtInteropSkipReason => "DbtInteropSkipReason", + DbtLsNode => "DbtLsNode", + DbtLsResult => "DbtLsResult", + DbtManifestIndex => "DbtManifestIndex", + DbtManifestModel => "DbtManifestModel", + DbtManifestSeed => "DbtManifestSeed", + DbtManifestSource => "DbtManifestSource", + DbtNodeExecutionResult => "DbtNodeExecutionResult", + DbtNodeMessage => "DbtNodeMessage", + DbtPlanEnvironment => "DbtPlanEnvironment", + DbtSqlbuildPlanRequest => "DbtSqlbuildPlanRequest", + DbtSqlbuildWorkContext => "DbtSqlbuildWorkContext", + DeclarationExpansionContext => "DeclarationExpansionContext", + DeclarationExplanation => "DeclarationExplanation", + DeclarationIdentity => "DeclarationIdentity", + DeclarationKind => "DeclarationKind", + DeclarationRecord => "DeclarationRecord", + DeclarationReport => "DeclarationReport", + DeclarationResolutionContext => "DeclarationResolutionContext", + DeclarationRuntimeProjection => "DeclarationRuntimeProjection", + DeclarationScopeBuild => "DeclarationScopeBuild", + DeclarationScopeResolver => "DeclarationScopeResolver", + DeclarationVisibility => "DeclarationVisibility", + DeclarationVisibilityIndex => "DeclarationVisibilityIndex", + DefaultsConfig => "DefaultsConfig", + DeferralInputs => "DeferralInputs", + DiagnosticPhase => "DiagnosticPhase", + DiagnosticSeverity => "DiagnosticSeverity", + DiffCommandRequest => "DiffCommandRequest", + DiffConfig => "DiffConfig", + DirectModelVersionIdentities => "DirectModelVersionIdentities", + DirectSemanticColumnUse => "DirectSemanticColumnUse", + DirectSourceFreshnessPlanningResult => "DirectSourceFreshnessPlanningResult", + DirectSourceFreshnessPropagationResult => "DirectSourceFreshnessPropagationResult", + DiscoveredAdapterFile => "DiscoveredAdapterFile", + DiscoveredAssetFunction => "DiscoveredAssetFunction", + DiscoveredAuditBlock => "DiscoveredAuditBlock", + DiscoveredAuditFactory => "DiscoveredAuditFactory", + DiscoveredAuditFile => "DiscoveredAuditFile", + DiscoveredCheckFunction => "DiscoveredCheckFunction", + DiscoveredCommandOutputSink => "DiscoveredCommandOutputSink", + DiscoveredCommandOutputSinkDeclaration => "DiscoveredCommandOutputSinkDeclaration", + DiscoveredConstantFile => "DiscoveredConstantFile", + DiscoveredEnumFile => "DiscoveredEnumFile", + DiscoveredEventExporter => "DiscoveredEventExporter", + DiscoveredEventExporterDeclaration => "DiscoveredEventExporterDeclaration", + DiscoveredHookFunction => "DiscoveredHookFunction", + DiscoveredLoaderFunction => "DiscoveredLoaderFunction", + DiscoveredMacroFile => "DiscoveredMacroFile", + DiscoveredMaterializationFile => "DiscoveredMaterializationFile", + DiscoveredModelSchemaFile => "DiscoveredModelSchemaFile", + DiscoveredProjectInputs => "DiscoveredProjectInputs", + DiscoveredProvider => "DiscoveredProvider", + DiscoveredProviderUsage => "DiscoveredProviderUsage", + DiscoveredPythonAssetMetadata => "DiscoveredPythonAssetMetadata", + DiscoveredPythonCheckMetadata => "DiscoveredPythonCheckMetadata", + DiscoveredPythonFunctionFile => "DiscoveredPythonFunctionFile", + DiscoveredPythonLoaderMetadata => "DiscoveredPythonLoaderMetadata", + DiscoveredPythonNode => "DiscoveredPythonNode", + DiscoveredPythonNodeFunctions => "DiscoveredPythonNodeFunctions", + DiscoveredPythonTaskMetadata => "DiscoveredPythonTaskMetadata", + DiscoveredRuntimeExtensions => "DiscoveredRuntimeExtensions", + DiscoveredSchemaFile => "DiscoveredSchemaFile", + DiscoveredSeedFile => "DiscoveredSeedFile", + DiscoveredSourceFile => "DiscoveredSourceFile", + DiscoveredSqlFunctionFile => "DiscoveredSqlFunctionFile", + DiscoveredSqlHookFile => "DiscoveredSqlHookFile", + DiscoveredSqlScenarioFile => "DiscoveredSqlScenarioFile", + DiscoveredSqlTestBlock => "DiscoveredSqlTestBlock", + DiscoveredSqlTestCase => "DiscoveredSqlTestCase", + DiscoveredSqlTestFile => "DiscoveredSqlTestFile", + DiscoveredTaskFunction => "DiscoveredTaskFunction", + DiscoveryFileFault => "DiscoveryFileFault", + DisplayOptions => "DisplayOptions", + Duration => "Duration", + DynamicColumnContractProof => "DynamicColumnContractProof", + DynamicColumnFamilyProof => "DynamicColumnFamilyProof", + EmptyCursorInputDecision => "EmptyCursorInputDecision", + EnumDeclaration => "EnumDeclaration", + EnumMember => "EnumMember", + EnumMemberMetadata => "EnumMemberMetadata", + EnumMetadata => "EnumMetadata", + EventExportSeverity => "EventExportSeverity", + ExecutionLimitsConfig => "ExecutionLimitsConfig", + ExpandedLintFile => "ExpandedLintFile", + ExpansionSpan => "ExpansionSpan", + Finding => "Finding", + Fingerprint => "Fingerprint", + FingerprintSet => "FingerprintSet", + FixVerdict => "FixVerdict", + FixtureColumnInference => "FixtureColumnInference", + FixtureColumnMetadata => "FixtureColumnMetadata", + FixtureRelationMetadata => "FixtureRelationMetadata", + FreshnessCommandRequest => "FreshnessCommandRequest", + FunctionArgument => "FunctionArgument", + FunctionChangeResult => "FunctionChangeResult", + FunctionPlanEntry => "FunctionPlanEntry", + FunctionReturnColumn => "FunctionReturnColumn", + FutureCursorAction => "FutureCursorAction", + FutureCursorSafetyEvidence => "FutureCursorSafetyEvidence", + FutureCursorsConfig => "FutureCursorsConfig", + GrantKind => "GrantKind", + GrantRecord => "GrantRecord", + GraphEdgeExclusion => "GraphEdgeExclusion", + GraphResourceKind => "GraphResourceKind", + HandBuiltAnalysis => "HandBuiltAnalysis", + HardCodedRelationOwnerKind => "HardCodedRelationOwnerKind", + HeaderSpan => "HeaderSpan", + HistoricalInput => "HistoricalInput", + HookReadEdge => "HookReadEdge", + HookReadType => "HookReadType", + InaccessibleReason => "InaccessibleReason", + InaccessibleRecord => "InaccessibleRecord", + IncrementalMode => "IncrementalMode", + IncrementalStrategy => "IncrementalStrategy", + InferredColumn => "InferredColumn", + InitialValidFrom => "InitialValidFrom", + IntegerValue => "IntegerValue", + IntegrationLoaderConfig => "IntegrationLoaderConfig", + InterpolationSite => "InterpolationSite", + InvalidSqlReferenceCall => "InvalidSqlReferenceCall", + JanitorCommandRequest => "JanitorCommandRequest", + JanitorConfig => "JanitorConfig", + JanitorInvocation => "JanitorInvocation", + JanitorSettings => "JanitorSettings", + LayoutConfig => "LayoutConfig", + LifecycleEventSinkFilterConfig => "LifecycleEventSinkFilterConfig", + LifecycleEventSinksConfig => "LifecycleEventSinksConfig", + LineageCommandRequest => "LineageCommandRequest", + LineageGraphViews => "LineageGraphViews", + LintBody => "LintBody", + LintConfig => "LintConfig", + LintEdit => "LintEdit", + LintFileRole => "LintFileRole", + LintRunResult => "LintRunResult", + LintSeverity => "LintSeverity", + LintViolation => "LintViolation", + LiteralSqlRelation => "LiteralSqlRelation", + LoadCommandRequest => "LoadCommandRequest", + LoadedMacro => "LoadedMacro", + LoaderConnectionMode => "LoaderConnectionMode", + LoaderDestinationParts => "LoaderDestinationParts", + LocalClonePolicy => "LocalClonePolicy", + LocalConfig => "LocalConfig", + LocalDbtConfig => "LocalDbtConfig", + LocalTargetConfig => "LocalTargetConfig", + MacroContext => "MacroContext", + MacroExpansionResult => "MacroExpansionResult", + MacroMetadata => "MacroMetadata", + ManualLocation => "ManualLocation", + MappedOffset => "MappedOffset", + MaterializationDefaultsConfig => "MaterializationDefaultsConfig", + MaterializationRetentionDefaults => "MaterializationRetentionDefaults", + MaterializationType => "MaterializationType", + MaximumStartPolicyInputs => "MaximumStartPolicyInputs", + MaximumStartSafetyEvidence => "MaximumStartSafetyEvidence", + MeasurementContract => "MeasurementContract", + MeasurementThresholdBound => "MeasurementThresholdBound", + MeasurementThresholds => "MeasurementThresholds", + MicrobatchLimitAction => "MicrobatchLimitAction", + MicrobatchLimitsConfig => "MicrobatchLimitsConfig", + MicrobatchStrategy => "MicrobatchStrategy", + MicrobatchesConfig => "MicrobatchesConfig", + MigrationCompatibility => "MigrationCompatibility", + MigrationCompatibilityResult => "MigrationCompatibilityResult", + MigrationDecision => "MigrationDecision", + MigrationDiscovery => "MigrationDiscovery", + MigrationEvent => "MigrationEvent", + MigrationPromotion => "MigrationPromotion", + MigrationRelation => "MigrationRelation", + MigrationsConfig => "MigrationsConfig", + MissingMigrationOriginPolicy => "MissingMigrationOriginPolicy", + MissingUpstream => "MissingUpstream", + Model => "Model", + ModelColumnLineage => "ModelColumnLineage", + ModelCursorSnapshot => "ModelCursorSnapshot", + ModelInputBuildContext => "ModelInputBuildContext", + ModelMigrationDeclaration => "ModelMigrationDeclaration", + ModelMigrationDiscovery => "ModelMigrationDiscovery", + ModelMigrationPlanEntry => "ModelMigrationPlanEntry", + ModelMigrationPlanning => "ModelMigrationPlanning", + ModelMigrationRequest => "ModelMigrationRequest", + ModelPlanContext => "ModelPlanContext", + ModelPlanEntry => "ModelPlanEntry", + ModelSchemaDeclaration => "ModelSchemaDeclaration", + ModelSql => "ModelSql", + ModelSqlAnalysis => "ModelSqlAnalysis", + ModelValidationRequest => "ModelValidationRequest", + ModuleStateFingerprint => "ModuleStateFingerprint", + ModuleStateTrace => "ModuleStateTrace", + MovePreview => "MovePreview", + NamedSqlHookEntry => "NamedSqlHookEntry", + NativeColumnQuery => "NativeColumnQuery", + NativeModelAnalyses => "NativeModelAnalyses", + NativeModelAnalysisRequest => "NativeModelAnalysisRequest", + NativeModelConfigInputs => "NativeModelConfigInputs", + NativeModelConfigSession => "NativeModelConfigSession", + NativePivotTables => "NativePivotTables", + NativeQueryColumns => "NativeQueryColumns", + NativeRulesEvaluation => "NativeRulesEvaluation", + NativeSqlTestArtifact => "NativeSqlTestArtifact", + NativeSqlTestAssembly => "NativeSqlTestAssembly", + NativeSqlTestChainRequest => "NativeSqlTestChainRequest", + NativeSqlTestFailure => "NativeSqlTestFailure", + NativeSqlTestPlan => "NativeSqlTestPlan", + NativeSqlTestPlanningRequest => "NativeSqlTestPlanningRequest", + NestedProgressChildRow => "NestedProgressChildRow", + NormalizedDbtProfileConnection => "NormalizedDbtProfileConnection", + OldNameView => "OldNameView", + OldNameViewAction => "OldNameViewAction", + OldNameViewDropReason => "OldNameViewDropReason", + OldNameViewEvent => "OldNameViewEvent", + OldNameViewEventType => "OldNameViewEventType", + OldNameViewHistory => "OldNameViewHistory", + OldNameViewPlanEntry => "OldNameViewPlanEntry", + OldNameViewPlanning => "OldNameViewPlanning", + OldNameViewStatus => "OldNameViewStatus", + OnSchemaChange => "OnSchemaChange", + OwnershipRoot => "OwnershipRoot", + OwnershipRootKind => "OwnershipRootKind", + ParsedCliInvocation => "ParsedCliInvocation", + ParsedScenarioArtifactName => "ParsedScenarioArtifactName", + ParsedSelector => "ParsedSelector", + PartialBuildPhaseTimings => "PartialBuildPhaseTimings", + PathSelector => "PathSelector", + PendingLintBody => "PendingLintBody", + PlanAction => "PlanAction", + PlanCommandRequest => "PlanCommandRequest", + PlanEntryBuildInputs => "PlanEntryBuildInputs", + PlanOutput => "PlanOutput", + PlanOutputExtras => "PlanOutputExtras", + PlanProviderUsage => "PlanProviderUsage", + PlanReason => "PlanReason", + PlanSafetyGates => "PlanSafetyGates", + PlanWarning => "PlanWarning", + PlannedSqlTests => "PlannedSqlTests", + PlannerChangeReconciliation => "PlannerChangeReconciliation", + PlannerChangeResults => "PlannerChangeResults", + PlannerEntryResults => "PlannerEntryResults", + PlannerIdentityContext => "PlannerIdentityContext", + PlannerModelEntryResults => "PlannerModelEntryResults", + PlannerOverrides => "PlannerOverrides", + PlannerPolicies => "PlannerPolicies", + PlannerRelationsContext => "PlannerRelationsContext", + PlannerResolvedActions => "PlannerResolvedActions", + PlannerRuntime => "PlannerRuntime", + PlannerScope => "PlannerScope", + PlannerScopePruningResult => "PlannerScopePruningResult", + PlannerScopeResolution => "PlannerScopeResolution", + PlannerSelection => "PlannerSelection", + PlannerWarehouseState => "PlannerWarehouseState", + PlaygroundCommandRequest => "PlaygroundCommandRequest", + PlaygroundTemplate => "PlaygroundTemplate", + PolyglotAnalysisDialect => "PolyglotAnalysisDialect", + PolyglotAnalysisResult => "PolyglotAnalysisResult", + PreparedLintFiles => "PreparedLintFiles", + PreparedSqlLint => "PreparedSqlLint", + PreparedSqlLintResult => "PreparedSqlLintResult", + Project => "Project", + ProjectConfig => "ProjectConfig", + ProjectGraph => "ProjectGraph", + ProjectPath => "ProjectPath", + PythonHookEntry => "PythonHookEntry", + PythonIdentityDependency => "PythonIdentityDependency", + PythonIdentityStatus => "PythonIdentityStatus", + PythonNodeDependencyEdge => "PythonNodeDependencyEdge", + PythonNodeGraph => "PythonNodeGraph", + PythonNodeIdentity => "PythonNodeIdentity", + PythonPlanEntry => "PythonPlanEntry", + PythonRunPhase => "PythonRunPhase", + PythonRunPlanOutputs => "PythonRunPlanOutputs", + PythonSqlReferenceReport => "PythonSqlReferenceReport", + PythonSqlRunLifecyclePlan => "PythonSqlRunLifecyclePlan", + PythonSqlRunSelection => "PythonSqlRunSelection", + QualifiedLineageColumn => "QualifiedLineageColumn", + QueryCostObservation => "QueryCostObservation", + QueryDiffPreparation => "QueryDiffPreparation", + QueryProjection => "QueryProjection", + QueryShape => "QueryShape", + RefactorCommandRequest => "RefactorCommandRequest", + RefactorProject => "RefactorProject", + ReferenceRename => "ReferenceRename", + ReferencesConfig => "ReferencesConfig", + RelatedLocation => "RelatedLocation", + RelationFixtureCompletion => "RelationFixtureCompletion", + RelationFixtureDiagnostic => "RelationFixtureDiagnostic", + RelationFixturePlanningContext => "RelationFixturePlanningContext", + ResolvedDbtConfig => "ResolvedDbtConfig", + ResolvedDbtProfileOutput => "ResolvedDbtProfileOutput", + ResolvedModelAction => "ResolvedModelAction", + ResolvedTableType => "ResolvedTableType", + ResolvedTimeTravelRetention => "ResolvedTimeTravelRetention", + ResolvedWarehouse => "ResolvedWarehouse", + ResourceCost => "ResourceCost", + ResourceIdentity => "ResourceIdentity", + ResourceKind => "ResourceKind", + ResourceRecord => "ResourceRecord", + RetentionDecreasePolicy => "RetentionDecreasePolicy", + RetentionDirection => "RetentionDirection", + RetentionPlanEntry => "RetentionPlanEntry", + RetentionPlanPhase => "RetentionPlanPhase", + Rule => "Rule", + RuleCase => "RuleCase", + RuleExemption => "RuleExemption", + RuleFactViews => "RuleFactViews", + RuleFile => "RuleFile", + RuleFixResult => "RuleFixResult", + RuleGuidance => "RuleGuidance", + RuleIgnore => "RuleIgnore", + RuleOption => "RuleOption", + RuleResult => "RuleResult", + RuleRunPlan => "RuleRunPlan", + RuleSubject => "RuleSubject", + RulesCacheConfig => "RulesCacheConfig", + RulesCommandRequest => "RulesCommandRequest", + RulesConfig => "RulesConfig", + RulesResult => "RulesResult", + RulesRunResult => "RulesRunResult", + ScenarioArtifactIdentity => "ScenarioArtifactIdentity", + ScenarioArtifactKind => "ScenarioArtifactKind", + ScenarioArtifactName => "ScenarioArtifactName", + ScenarioAssertionExpectationPlan => "ScenarioAssertionExpectationPlan", + ScenarioCaptureCommandRequest => "ScenarioCaptureCommandRequest", + ScenarioConfig => "ScenarioConfig", + ScenarioExecutionPlan => "ScenarioExecutionPlan", + ScenarioExpectedExpectationPlan => "ScenarioExpectedExpectationPlan", + ScenarioFixturePlan => "ScenarioFixturePlan", + ScenarioGraphPlan => "ScenarioGraphPlan", + ScenarioRelationMap => "ScenarioRelationMap", + ScenarioRelationPlan => "ScenarioRelationPlan", + ScenarioSnapshotLimitInputs => "ScenarioSnapshotLimitInputs", + ScenarioSnapshotLimitsConfig => "ScenarioSnapshotLimitsConfig", + ScenarioTestCommandRequest => "ScenarioTestCommandRequest", + SchemaAction => "SchemaAction", + SchemaActionKind => "SchemaActionKind", + SchemaAuditInstance => "SchemaAuditInstance", + SchemaChangeKind => "SchemaChangeKind", + SchemaColumn => "SchemaColumn", + SchemaColumnSource => "SchemaColumnSource", + SchemaDynamicColumnFamily => "SchemaDynamicColumnFamily", + SchemaFinding => "SchemaFinding", + SchemaModelEntry => "SchemaModelEntry", + SchemaSeedEntry => "SchemaSeedEntry", + ScopeBrowseResult => "ScopeBrowseResult", + ScopeChainEntry => "ScopeChainEntry", + ScopeCommandRequest => "ScopeCommandRequest", + ScopeCompleteness => "ScopeCompleteness", + ScopeDiagnostic => "ScopeDiagnostic", + ScopeDiagnosticCode => "ScopeDiagnosticCode", + ScopeDiagnosticSeverity => "ScopeDiagnosticSeverity", + ScopeFolder => "ScopeFolder", + ScopeIndex => "ScopeIndex", + ScopeKind => "ScopeKind", + ScopeListResult => "ScopeListResult", + ScopeLookup => "ScopeLookup", + ScopeReport => "ScopeReport", + ScopeReportFilters => "ScopeReportFilters", + ScopeResourceReport => "ScopeResourceReport", + ScopeSection => "ScopeSection", + ScopeSourceLocation => "ScopeSourceLocation", + ScopeTargetQuery => "ScopeTargetQuery", + ScopesConfig => "ScopesConfig", + SeedCommandRequest => "SeedCommandRequest", + SeedCsvSettings => "SeedCsvSettings", + SeedInvocation => "SeedInvocation", + SeedPlanEntry => "SeedPlanEntry", + SelectStarAllow => "SelectStarAllow", + SelectedDbtProfileOutput => "SelectedDbtProfileOutput", + SelectionStalenessGraph => "SelectionStalenessGraph", + SelectionStalenessNodeKey => "SelectionStalenessNodeKey", + SelectionStalenessWarning => "SelectionStalenessWarning", + SelectorExpansion => "SelectorExpansion", + SelectorFileSummary => "SelectorFileSummary", + SelectorKind => "SelectorKind", + SettingsConfig => "SettingsConfig", + SinksConfig => "SinksConfig", + SkillMaintenanceResult => "SkillMaintenanceResult", + SkillUpdateResult => "SkillUpdateResult", + SnapshotSchemaChangePolicy => "SnapshotSchemaChangePolicy", + SnapshotStrategy => "SnapshotStrategy", + SnapshotsConfig => "SnapshotsConfig", + SourceColumnEntry => "SourceColumnEntry", + SourceEntry => "SourceEntry", + SourceFreshnessAgePolicy => "SourceFreshnessAgePolicy", + SourceFreshnessAgeStatus => "SourceFreshnessAgeStatus", + SourceFreshnessConfig => "SourceFreshnessConfig", + SourceFreshnessIdentity => "SourceFreshnessIdentity", + SourceFreshnessObservation => "SourceFreshnessObservation", + SourceFreshnessRecord => "SourceFreshnessRecord", + SourceFreshnessRenderers => "SourceFreshnessRenderers", + SourceFreshnessSet => "SourceFreshnessSet", + SourceFreshnessStrategy => "SourceFreshnessStrategy", + SourceFreshnessUnknown => "SourceFreshnessUnknown", + SourceFreshnessUnknownReason => "SourceFreshnessUnknownReason", + SourceFreshnessValueKind => "SourceFreshnessValueKind", + SourceLoadPlanEntry => "SourceLoadPlanEntry", + SourceLocation => "SourceLocation", + SourceWriteStrategy => "SourceWriteStrategy", + SqlBindingDiagnostic => "SqlBindingDiagnostic", + SqlBindingResult => "SqlBindingResult", + SqlExpansionContext => "SqlExpansionContext", + SqlExpansionReuse => "SqlExpansionReuse", + SqlHookEntry => "SqlHookEntry", + SqlLexicalSyntax => "SqlLexicalSyntax", + SqlLogicalType => "SqlLogicalType", + SqlNode => "SqlNode", + SqlReferenceKind => "SqlReferenceKind", + SqlReferenceScan => "SqlReferenceScan", + SqlRulesEvaluation => "SqlRulesEvaluation", + SqlSchemaValidationRequest => "SqlSchemaValidationRequest", + SqlTestAssertionStep => "SqlTestAssertionStep", + SqlTestParameterDeclaration => "SqlTestParameterDeclaration", + SqlTestPlanEntry => "SqlTestPlanEntry", + SqlTestPlanResult => "SqlTestPlanResult", + SqlTestRulesConfig => "SqlTestRulesConfig", + SqlTestSelection => "SqlTestSelection", + SqlValue => "SqlValue", + SqlValueKind => "SqlValueKind", + SqlValueLimits => "SqlValueLimits", + StartCursorsConfig => "StartCursorsConfig", + StateSqlValueType => "StateSqlValueType", + StatementLedgerEntry => "StatementLedgerEntry", + StaticCommandContext => "StaticCommandContext", + StaticMacroExport => "StaticMacroExport", + StaticMacroFault => "StaticMacroFault", + StaticMacroInventory => "StaticMacroInventory", + TableType => "TableType", + TableTypeDowngradePolicy => "TableTypeDowngradePolicy", + TableTypePlanEntry => "TableTypePlanEntry", + TableTypeSource => "TableTypeSource", + TargetConfig => "TargetConfig", + TargetDiffConfig => "TargetDiffConfig", + TargetWarehousesConfig => "TargetWarehousesConfig", + TestCommandRequest => "TestCommandRequest", + TestInvocation => "TestInvocation", + ThresholdOverride => "ThresholdOverride", + TimeTravelRetentionSource => "TimeTravelRetentionSource", + TimestampValue => "TimestampValue", + TolerantScopeDiscovery => "TolerantScopeDiscovery", + UnitTestSelectorSplit => "UnitTestSelectorSplit", + UsageKind => "UsageKind", + UsageRecord => "UsageRecord", + VisibilityProvenance => "VisibilityProvenance", + VisibilityReason => "VisibilityReason", + VisibilityRecord => "VisibilityRecord", + VisibilityResolution => "VisibilityResolution", + VisibleModelDeclarations => "VisibleModelDeclarations", + WarehouseFingerprints => "WarehouseFingerprints", + WarehouseGroup => "WarehouseGroup", + WarehouseSnapshot => "WarehouseSnapshot", + WarningSeverity => "WarningSeverity", + WrittenTarget => "WrittenTarget", + } +} + +native_ids! { + /// A native error class, registered from the error specs; variants drop the `Error` suffix. + pub(crate) enum ErrorId { + CliUser => "CliUserError", + CompileInput => "CompileInputError", + ConfigValueType => "ConfigValueTypeError", + ContractAdoption => "ContractAdoptionError", + DbtInteropArgument => "DbtInteropArgumentError", + DbtInteropConfig => "DbtInteropConfigError", + DbtInterop => "DbtInteropError", + DbtInteropRuntime => "DbtInteropRuntimeError", + DeclarationParse => "DeclarationParseError", + DiffSizeGuard => "DiffSizeGuardError", + DiscoveryConflict => "DiscoveryConflictError", + Discovery => "DiscoveryError", + EventExporterDiscovery => "EventExporterDiscoveryError", + FactDigest => "FactDigestError", + FingerprintInput => "FingerprintInputError", + FutureCursorSafety => "FutureCursorSafetyError", + HostCancelled => "HostCancelledError", + InvalidQualifiedIdentity => "InvalidQualifiedIdentityError", + InvalidScopePath => "InvalidScopePathError", + Lint => "LintError", + LintStopped => "LintStoppedError", + MacroArgument => "MacroArgumentError", + MaximumAutomaticStart => "MaximumAutomaticStartError", + MeasurementAudit => "MeasurementAuditError", + MigrationState => "MigrationStateError", + ModelSqlParse => "ModelSqlParseError", + NativeLint => "NativeLintError", + NativeSqlTestPlanning => "NativeSqlTestPlanningError", + NativeStageMismatch => "NativeStageMismatchError", + NonHermeticRule => "NonHermeticRuleError", + OpaqueModuleState => "OpaqueModuleStateError", + PlannerInput => "PlannerInputError", + ProjectCompile => "ProjectCompileError", + ProjectConfig => "ProjectConfigError", + ProjectPath => "ProjectPathError", + ProjectPythonPath => "ProjectPythonPathError", + ProviderDiscovery => "ProviderDiscoveryError", + PythonNodeDiscovery => "PythonNodeDiscoveryError", + QueryDiffExecution => "QueryDiffExecutionError", + QueryDiffIncomplete => "QueryDiffIncompleteError", + QueryDiffOutcome => "QueryDiffOutcomeError", + QueryExecution => "QueryExecutionError", + QueryFingerprint => "QueryFingerprintError", + RefactorIO => "RefactorIOError", + RefactorValue => "RefactorValueError", + ResourceIdentity => "ResourceIdentityError", + RuleAssertion => "RuleAssertionError", + RuleUsage => "RuleUsageError", + RulesConfig => "RulesConfigError", + Rules => "RulesError", + SchemaParse => "SchemaParseError", + Scope => "ScopeError", + ScopeValidation => "ScopeValidationError", + SeedDiscovery => "SeedDiscoveryError", + SourceParse => "SourceParseError", + SpecConfig => "SpecConfigError", + SqlAuditParse => "SqlAuditParseError", + SqlHookParse => "SqlHookParseError", + SqlScenarioParse => "SqlScenarioParseError", + SqlTestParse => "SqlTestParseError", + SqlTestReference => "SqlTestReferenceError", + StagedArtifactsChanged => "StagedArtifactsChangedError", + UnindexableScope => "UnindexableScopeError", + UnknownLineageDialect => "UnknownLineageDialectError", + UnsupportedLexicalSyntax => "UnsupportedLexicalSyntaxError", + } +} + +native_ids! { + /// A native function, class or value native code reaches through the module. + pub(crate) enum NativeAttr { + AnalyzeCompileProject => "analyze_compile_project", + AnalyzeNativeModelSql => "analyze_native_model_sql", + ApplyCommandWarehouse => "apply_command_warehouse", + ApplyConfiguredMicrobatchLimits => "apply_configured_microbatch_limits", + ApplyDeferredLocations => "apply_deferred_locations", + ApplyLintSuppressions => "apply_lint_suppressions", + ApplyMaximumStartPolicy => "apply_maximum_start_policy", + ApplyTypedCursorReplayPolicy => "apply_typed_cursor_replay_policy", + ApplyUnitTestSelectors => "apply_unit_test_selectors", + AssembleBasePlanOutput => "assemble_base_plan_output", + AssembleModelPlanEntry => "assemble_model_plan_entry", + AssembleNativeProjectResources => "assemble_native_project_resources", + AssembleNativeSqlTests => "assemble_native_sql_tests", + AssembleProject => "assemble_project", + AssertNoUnresolvedSqlMarkers => "assert_no_unresolved_sql_markers", + AttachChecks => "attach_checks", + AttachResources => "attach_resources", + AttachSqlbuildPlanOutput => "attach_sqlbuild_plan_output", + AuditIsSelected => "audit_is_selected", + AuditProgressReporter => "AuditProgressReporter", + BindingPositions => "BindingPositions", + BuildAuditCommandPlan => "build_audit_command_plan", + BuildCompileDbtManifestIndex => "build_compile_dbt_manifest_index", + BuildCompileInputs => "build_compile_inputs", + BuildCompiledFunctionFingerprintSql => "build_compiled_function_fingerprint_sql", + BuildCompiledProject => "build_compiled_project", + BuildCursorInputRelations => "build_cursor_input_relations", + BuildDagJson => "build_dag_json", + BuildDbtCombinedGraph => "build_dbt_combined_graph", + BuildDbtInitProject => "build_dbt_init_project", + BuildDbtInitProjectToml => "build_dbt_init_project_toml", + BuildDeclarationScope => "build_declaration_scope", + BuildDeferredLocations => "build_deferred_locations", + BuildDirectModelVersionIdentities => "build_direct_model_version_identities", + BuildDirectSemanticUses => "build_direct_semantic_uses", + BuildDisplayOnlySqlbuildPlan => "build_display_only_sqlbuild_plan", + BuildDownstreamDeps => "build_downstream_deps", + BuildEffectiveConnection => "build_effective_connection", + BuildEffectiveConnectionConfig => "build_effective_connection_config", + BuildEffectiveRuntimeConfig => "build_effective_runtime_config", + BuildEffectiveSettings => "build_effective_settings", + BuildEffectiveSettingsConfig => "build_effective_settings_config", + BuildEffectiveTargetConfig => "build_effective_target_config", + BuildEffectiveTargetNamespace => "build_effective_target_namespace", + BuildEffectiveVars => "build_effective_vars", + BuildExecutionEdgeOrigins => "build_execution_edge_origins", + BuildExecutionPlan => "build_execution_plan", + BuildExecutionUpstreamDeps => "build_execution_upstream_deps", + BuildFailedSqlbuildModelNames => "build_failed_sqlbuild_model_names", + BuildFunctionLocalHashes => "build_function_local_hashes", + BuildFunctionLocations => "build_function_locations", + BuildIdentity => "BUILD_IDENTITY", + BuildIntermediateSourceMap => "build_intermediate_source_map", + BuildLintExpansionContext => "build_lint_expansion_context", + BuildManifest => "build_manifest", + BuildMergedDbtExecutionArgv => "build_merged_dbt_execution_argv", + BuildMigrationFingerprint => "build_migration_fingerprint", + BuildModelInputs => "build_model_inputs", + BuildModelLocations => "build_model_locations", + BuildModelVersionIdentityMetadataJson => "build_model_version_identity_metadata_json", + BuildModelWarnings => "build_model_warnings", + BuildPhaseTimingTracker => "BuildPhaseTimingTracker", + BuildPlanEntries => "build_plan_entries", + BuildPlanOutput => "build_plan_output", + BuildPlannerEntryResults => "build_planner_entry_results", + BuildPlannerIdentityContext => "build_planner_identity_context", + BuildPlannerRelationsContext => "build_planner_relations_context", + BuildPlannerScope => "build_planner_scope", + BuildPlannerSourceFreshnessResult => "build_planner_source_freshness_result", + BuildProgressCallbacks => "BuildProgressCallbacks", + BuildProjectColumnLineage => "build_project_column_lineage", + BuildProjectGraph => "build_project_graph", + BuildProjectGraphFromCompiledProject => "build_project_graph_from_compiled_project", + BuildProjectGraphWithAnalysisSelection => "build_project_graph_with_analysis_selection", + BuildProjectRelationTargets => "build_project_relation_targets", + BuildPythonRelationTargets => "build_python_relation_targets", + BuildPythonRunPlanOutputs => "build_python_run_plan_outputs", + BuildPythonSqlRunLifecycle => "build_python_sql_run_lifecycle", + BuildRelationCommandPlan => "build_relation_command_plan", + BuildRelationFixtureCompletion => "build_relation_fixture_completion", + BuildRelationFixtureContext => "build_relation_fixture_context", + BuildRuleCatalogue => "build_rule_catalogue", + BuildRulesRequest => "build_rules_request", + BuildScenarioExecutionPlan => "build_scenario_execution_plan", + BuildScenarioHashIndex => "build_scenario_hash_index", + BuildScenarioRelationMap => "build_scenario_relation_map", + BuildSeedCommandPlan => "build_seed_command_plan", + BuildSeedIdentity => "build_seed_identity", + BuildSeedLocations => "build_seed_locations", + BuildSelectedAuditEntries => "build_selected_audit_entries", + BuildSourceLoadEntries => "build_source_load_entries", + BuildSourceLoadMap => "build_source_load_map", + BuildSourceReadMap => "build_source_read_map", + BuildSqlbuildPlanOutput => "build_sqlbuild_plan_output", + BuildStaleOutOfSelectionWarnings => "build_stale_out_of_selection_warnings", + BuildStaticPipelineResult => "build_static_pipeline_result", + BuildTestCommandPlan => "build_test_command_plan", + BuildUpstreamIntermediateSourceMap => "build_upstream_intermediate_source_map", + CheckBuildability => "check_buildability", + CheckCompileReuse => "check_compile_reuse", + CheckCursorTypeConsistency => "check_cursor_type_consistency", + CheckFullRebuildAllowed => "check_full_rebuild_allowed", + CheckMigrationCompatibility => "check_migration_compatibility", + CheckSelectedScopeBuildability => "check_selected_scope_buildability", + ClassifyCursorSqlType => "classify_cursor_sql_type", + CliStyle => "CliStyle", + CollectCompileDiagnostics => "collect_compile_diagnostics", + CollectLintProjectFiles => "collect_lint_project_files", + CommandWarehouseScopeContext => "_command_warehouse_scope_context", + CompareContracts => "compare_contracts", + CompileDbtInteropProject => "compile_dbt_interop_project", + CompileFrontier => "compile_frontier", + CompileInputsView => "compile_inputs_view", + CompileProjectPhase => "compile_project_phase", + CompileReuseExcludedRootDirectories => "COMPILE_REUSE_EXCLUDED_ROOT_DIRECTORIES", + CompileReuseRacyWindowNs => "COMPILE_REUSE_RACY_WINDOW_NS", + CompileStaticCommandContext => "compile_static_command_context", + CompiledCodeIdentity => "compiled_code_identity", + CompiledModelRecord => "compiled_model_record", + CompiledProjectFacts => "compiled_project_facts", + CompiledProjectRecord => "compiled_project_record", + CompiledRelationKeys => "compiled_relation_keys", + CompleteSemanticProject => "complete_semantic_project", + ComputeCursorBounds => "compute_cursor_bounds", + ComputeModelCursorBounds => "compute_model_cursor_bounds", + ComputeQueryHash => "compute_query_hash", + ConnectionProgressReporter => "ConnectionProgressReporter", + ContentDigest => "content_digest", + ContextStoreToken => "context_store_token", + CostArtifactError => "CostArtifactError", + CostContext => "CostContext", + CostOutputPayload => "cost_output_payload", + CurrentNativeProgressProjector => "current_native_progress_projector", + CurrentTerminalEventIndex => "current_terminal_event_index", + CursorCompare => "cursor_compare", + CursorExclusiveToInclusive => "cursor_exclusive_to_inclusive", + CursorInputsWithoutRows => "cursor_inputs_without_rows", + CursorObservedPartition => "cursor_observed_partition", + CursorParse => "cursor_parse", + CursorRender => "cursor_render", + CursorSentinelToToken => "cursor_sentinel_to_token", + CustomHostObserver => "custom_host_observer", + CustomRuleIdentity => "custom_rule_identity", + CustomRuleImplementationFingerprint => "custom_rule_implementation_fingerprint", + CustomRuleImportClosure => "custom_rule_import_closure", + DbtEventStream => "DbtEventStream", + DbtExecutionLsArgv => "dbt_execution_ls_argv", + DbtFailureDetail => "dbt_failure_detail", + DbtInitProgressReporter => "DbtInitProgressReporter", + DbtRunner => "DbtRunner", + DebugDbtFromProject => "debug_dbt_from_project", + DecideModelMigration => "decide_model_migration", + DeclarationRows => "declaration_rows", + DeclaredColumnAliases => "declared_column_aliases", + DeclaredRelationKeys => "declared_relation_keys", + DeclaredRelations => "declared_relations", + DescribeConnectionWarehouse => "describe_connection_warehouse", + DescribeFullRebuildCause => "describe_full_rebuild_cause", + DetectChanges => "detect_changes", + DetectModelChanges => "detect_model_changes", + DetectModelChangesInScope => "detect_model_changes_in_scope", + DetectStaleWarningChanges => "detect_stale_warning_changes", + DirectorySnapshotScope => "directory_snapshot_scope", + DiscoverEventExporterFunctions => "discover_event_exporter_functions", + DiscoverHookFunctions => "discover_hook_functions", + DiscoverMaterializationFiles => "discover_materialization_files", + DiscoverModelFiles => "discover_model_files", + DiscoverModelMigrations => "discover_model_migrations", + DiscoverModelSchemaFiles => "discover_model_schema_files", + DiscoverProjectInputs => "discover_project_inputs", + DiscoverProviderClasses => "discover_provider_classes", + DiscoverPythonHookFiles => "discover_python_hook_files", + DiscoverPythonModuleFiles => "discover_python_module_files", + DiscoverPythonNodeFunctions => "discover_python_node_functions", + DiscoverSchemaFiles => "discover_schema_files", + DiscoverSourceFiles => "discover_source_files", + DiscoverTestFiles => "discover_test_files", + DynamicContractProof => "dynamic_contract_proof", + EarlyLintExpansions => "early_lint_expansions", + EditContractModel => "edit_contract_model", + EditContractSource => "edit_contract_source", + EffectiveConfigConnection => "effective_config_connection", + EffectiveConfigSettings => "effective_config_settings", + EffectiveConfigVars => "effective_config_vars", + EffectiveTargetConfig => "effective_target_config", + EffectivelyFullRefreshedModelNames => "effectively_full_refreshed_model_names", + EmptyCursorWindow => "empty_cursor_window", + EmptyInputRebuildRefusal => "empty_input_rebuild_refusal", + EncodeSqlRuleFindings => "encode_sql_rule_findings", + EnforceModelExecutionLimit => "enforce_model_execution_limit", + EnforcePlanSafetyPolicies => "enforce_plan_safety_policies", + EvaluateModelContracts => "evaluate_model_contracts", + EvaluateRules => "evaluate_rules", + EvaluateRulesRequest => "evaluate_rules_request", + EventExporterCommandScope => "EventExporterCommandScope", + EventExporterCommandScopeFunction => "event_exporter_command_scope", + EventExporterDispatcher => "EventExporterDispatcher", + ExecuteDbtCommands => "execute_dbt_commands", + ExecuteDbtInteropFromProject => "execute_dbt_interop_from_project", + ExecuteDbtJsonEventStream => "execute_dbt_json_event_stream", + ExecuteDbtSqlbuildWork => "execute_dbt_sqlbuild_work", + ExecuteDbtWithoutStateTracking => "execute_dbt_without_state_tracking", + ExecutionEventWriter => "ExecutionEventWriter", + ExpandFileBodies => "expand_file_bodies", + ExpandProjectUpstreamKeys => "expand_project_upstream_keys", + ExpandSqlMacros => "expand_sql_macros", + ExpandSqlWithSpans => "expand_sql_with_spans", + ExplicitReferenceReport => "explicit_reference_report", + ExplicitReferencesHelp => "explicit_references_help", + ExternalIdentifiersForHeaders => "external_identifiers_for_headers", + ExternallyDescribedModels => "externally_described_models", + ExtractSeedColumns => "extract_seed_columns", + FinalizeRuleFindingsRows => "finalize_rule_findings_rows", + FindPythonStringLocations => "find_python_string_locations", + FinishRuleRun => "finish_rule_run", + FixtureColumnInferences => "FixtureColumnInferences", + FixtureReadCache => "_fixture_read_cache", + FormatAuditExecutionJson => "format_audit_execution_json", + FormatBuildExecutionJson => "format_build_execution_json", + FormatBuildFooter => "format_build_footer", + FormatCloneExecutionJson => "format_clone_execution_json", + FormatCompileErrorJson => "format_compile_error_json", + FormatCostBreakdown => "format_cost_breakdown", + FormatCostHistory => "format_cost_history", + FormatCostHistoryJson => "format_cost_history_json", + FormatCostJson => "format_cost_json", + FormatDegradableBuildExecutionJson => "format_degradable_build_execution_json", + FormatExecutionHeader => "format_execution_header", + FormatExpectationDetail => "format_expectation_detail", + FormatExpectationName => "format_expectation_name", + FormatLintSqlBodies => "format_lint_sql_bodies", + FormatLoadExecutionJson => "format_load_execution_json", + FormatNativeHeaders => "format_native_headers", + FormatPlan => "format_plan", + FormatPlanJson => "format_plan_json", + FormatRedundantFixtureNulls => "format_redundant_fixture_nulls", + FormatRule => "format_rule", + FormatRulesResult => "format_rules_result", + FormatScenarioExecutionJson => "format_scenario_execution_json", + FormatScenarioSnapshotExecutionJson => "format_scenario_snapshot_execution_json", + FormatSeedExecutionJson => "format_seed_execution_json", + FormatSkippedTypeProofRules => "format_skipped_type_proof_rules", + FormatTestExecutionJson => "format_test_execution_json", + GatherDeferredRelations => "gather_deferred_relations", + GatherPlannerWarehouseState => "gather_planner_warehouse_state", + GatherRedirectedCursorSnapshots => "gather_redirected_cursor_snapshots", + GatherSourceColumns => "gather_source_columns", + GatherSourceInspection => "gather_source_inspection", + GetConfigCursorBound => "get_config_cursor_bound", + GetConfigStr => "get_config_str", + GetMaterializationType => "get_materialization_type", + GetSemanticSourceColumns => "get_semantic_source_columns", + HardCodedRelationRemedy => "hard_coded_relation_remedy", + IdenticalRenames => "identical_renames", + InferFixtureColumnFacts => "infer_fixture_column_facts", + InferNativeExpressionSourceShapes => "infer_native_expression_source_shapes", + InferNativeQueryColumnRows => "infer_native_query_column_rows", + InferNativeQueryColumns => "infer_native_query_columns", + InputsWith => "inputs_with", + InstallRulesSkills => "install_rules_skills", + KnownDeclaredTypes => "known_declared_types", + KnownFunctionNames => "known_function_names", + LaunchRecordOffered => "_launch_record_offered", + LaunchRecordWithheld => "_launch_record_withheld", + LineageGraphNames => "lineage_graph_names", + LintNativeHeaders => "lint_native_headers", + LintNativeSqlBodies => "lint_native_sql_bodies", + LintStopScopeFunction => "lint_stop_scope", + ListCostRuns => "list_cost_runs", + LoadCompiledDbtManifest => "load_compiled_dbt_manifest", + LoadDbtManifestIndex => "load_dbt_manifest_index", + LoadLocalConfigFile => "load_local_config_file", + LoadOrBuildScopeIndex => "load_or_build_scope_index", + LoadProgressReporter => "LoadProgressReporter", + LoadProjectConfigFile => "load_project_config_file", + LoadRulesConfig => "load_rules_config", + LoadSqlRuleCache => "load_sql_rule_cache", + LoaderDestinationPartsFunction => "loader_destination_parts", + LocateModelBindingDiagnostics => "locate_model_binding_diagnostics", + LookupSqlRuleCache => "lookup_sql_rule_cache", + MachineOutputMode => "machine_output_mode", + MacroStoreToken => "macro_store_token", + MaintainSqlbuildSkills => "maintain_sqlbuild_skills", + MatchMigrationFingerprints => "match_migration_fingerprints", + MatchProjectRelation => "match_project_relation", + MatchProjectSelector => "match_project_selector", + MatchingDynamicFamilies => "matching_dynamic_families", + MaximumAllowedStart => "maximum_allowed_start", + MeasurementBodyRanges => "measurement_body_ranges", + MergeRecomputedModelChanges => "merge_recomputed_model_changes", + MicrobatchGuidance => "microbatch_guidance", + MigrationDestinationFingerprints => "migration_destination_fingerprints", + MigrationFingerprintCache => "MigrationFingerprintCache", + MigrationFingerprintRefNames => "migration_fingerprint_ref_names", + MigrationMissingOriginPolicy => "migration_missing_origin_policy", + MigrationPlanningDatabase => "migration_planning_database", + MigrationProjectSchemas => "migration_project_schemas", + MigrationRedirectedCursorSnapshots => "migration_redirected_cursor_snapshots", + MigrationRequireSameDatabase => "migration_require_same_database", + MigrationResolveOrigin => "migration_resolve_origin", + MigrationStateInspection => "MigrationStateInspection", + MigrationWithExecution => "migration_with_execution", + MissingUpstreamMessage => "missing_upstream_message", + ModelDefinitionHash => "model_definition_hash", + ModelHeaderFailure => "model_header_failure", + ModelOutputColumns => "model_output_columns", + ModelPlaceholders => "model_placeholders", + ModelPythonHookNames => "model_python_hook_names", + ModelSyntaxChecks => "model_syntax_checks", + NamedDeclarationRoleKind => "named_declaration_role_kind", + NativeCompiledModel => "NativeCompiledModel", + NativeCompiledProject => "NativeCompiledProject", + NativeCompiledSeed => "NativeCompiledSeed", + NativeDeclarationContexts => "NativeDeclarationContexts", + NativeModelConfigBuilder => "NativeModelConfigBuilder", + NativeModelInput => "NativeModelInput", + NativeModelLoop => "NativeModelLoop", + NativeModelValidator => "NativeModelValidator", + NativeProgressProjector => "NativeProgressProjector", + NativeProgressProjectorContext => "_native_progress_projector_context", + NestedCommandProgressCallbacks => "NestedCommandProgressCallbacks", + NeutralizeLintAuditParameters => "neutralize_lint_audit_parameters", + NeutralizeLintContextInterpolation => "neutralize_lint_context_interpolation", + NormalizeAnalysisSql => "normalize_analysis_sql", + NormalizeCursorSnapshotGrain => "normalize_cursor_snapshot_grain", + NormalizeDbtProfileConnection => "normalize_dbt_profile_connection", + OldNameClaimErrors => "old_name_claim_errors", + OldNameReferenceMessage => "old_name_reference_message", + OldNameSchemasToRead => "old_name_schemas_to_read", + OldNameViewSkipReason => "old_name_view_skip_reason", + ParseDbtLsJsonLines => "parse_dbt_ls_json_lines", + ParseHeaderValues => "parse_header_values", + ParseModelHeaders => "parse_model_headers", + ParseProjectSelector => "parse_project_selector", + ParseQueryShape => "parse_query_shape", + PlanColumnMigrations => "plan_column_migrations", + PlanCompiledSqlTestArtifacts => "plan_compiled_sql_test_artifacts", + PlanCompiledSqlTests => "plan_compiled_sql_tests", + PlanDbtInteropCommand => "plan_dbt_interop_command", + PlanDbtInteropFromProject => "plan_dbt_interop_from_project", + PlanHasExecutableWork => "plan_has_executable_work", + PlanModel => "plan_model", + PlanModelMigrations => "plan_model_migrations", + PlanOldNameViews => "plan_old_name_views", + PlanReferenceRenames => "plan_reference_renames", + PlanRetention => "plan_retention", + PlanRuleFixes => "plan_rule_fixes", + PlanRuleRun => "plan_rule_run", + PlanSelectedSqlTests => "plan_selected_sql_tests", + PlanSelectedSqlTestsInto => "plan_selected_sql_tests_into", + PlanSqlTests => "plan_sql_tests", + PlanSqlTestsNatively => "plan_sql_tests_natively", + PlanStaticSqlTests => "plan_static_sql_tests", + PlanTableTypes => "plan_table_types", + PlanningProgressReporter => "PlanningProgressReporter", + PrepareExpandedFiles => "prepare_expanded_files", + PrepareLintSql => "prepare_lint_sql", + PrepareLintSqlBatch => "prepare_lint_sql_batch", + PrepareModelHeaderTokens => "prepare_model_header_tokens", + PrepareNativeHeaderCache => "prepare_native_header_cache", + PrepareSqlRules => "prepare_sql_rules", + PreparedCompileArtifacts => "PreparedCompileArtifacts", + ProjectLineageViews => "project_lineage_views", + ProjectRelativePath => "project_relative_path", + ProjectWith => "project_with", + PromotionConflictDiagnostics => "promotion_conflict_diagnostics", + ProveNativeDynamicContracts => "prove_native_dynamic_contracts", + PrunePlannerExecutionScope => "prune_planner_execution_scope", + PublicDeclarationIndexes => "public_declaration_indexes", + PublicModelSchemaIndex => "public_model_schema_index", + PythonReadSourceNamesFor => "python_read_source_names_for", + ReadCostRun => "read_cost_run", + ReadLatestFingerprints => "read_latest_fingerprints", + ReadOldNameViewHistories => "read_old_name_view_histories", + ReadSelectedSeedFingerprints => "read_selected_seed_fingerprints", + ReadsOutsideTargetLineage => "reads_outside_target_lineage", + RebindDeclarationScope => "rebind_declaration_scope", + ReconcileExecutionChanges => "reconcile_execution_changes", + RecordCompileInputReads => "record_compile_input_reads", + RecordModelAnalyses => "record_model_analyses", + RecordSettingsClassRead => "record_settings_class_read", + RecordTemplateReads => "record_template_reads", + RejectUnitTestSelectors => "reject_unit_test_selectors", + RejectUnparseableHeaderRewrites => "reject_unparseable_header_rewrites", + RelationLineageFingerprint => "relation_lineage_fingerprint", + RemainingContractFindings => "remaining_contract_findings", + RenameHints => "rename_hints", + RenamesExplainChange => "renames_explain_change", + RenderAuditSql => "render_audit_sql", + RenderConstantDeclaration => "render_constant_declaration", + RenderDbtExecutionSummaryFooter => "render_dbt_execution_summary_footer", + RenderDbtInteropPlan => "render_dbt_interop_plan", + RenderDbtNodeResult => "render_dbt_node_result", + RenderDiffErrorJson => "render_diff_error_json", + RenderProjectVarText => "render_project_var_text", + RenderSourceReadRelation => "render_source_read_relation", + RenderSourceRelation => "render_source_relation", + ReplacesIncrementalRelation => "replaces_incremental_relation", + ReportCompileDiagnostic => "report_compile_diagnostic", + ResolveAnalysisModelNames => "resolve_analysis_model_names", + ResolveAttachmentKind => "resolve_attachment_kind", + ResolveBoundedCursorOverride => "resolve_bounded_cursor_override", + ResolveCloneBoundary => "resolve_clone_boundary", + ResolveCompileAnalysisSelection => "resolve_compile_analysis_selection", + ResolveCompiledSqlTestChains => "resolve_compiled_sql_test_chains", + ResolveConfigFilePath => "resolve_config_file_path", + ResolveConfiguredRuleSelection => "resolve_configured_rule_selection", + ResolveCostRun => "resolve_cost_run", + ResolveCursorInputRoles => "resolve_cursor_input_roles", + ResolveCursorIntrinsics => "resolve_cursor_intrinsics", + ResolveDbtConfig => "resolve_dbt_config", + ResolveDbtConnectionConfig => "resolve_dbt_connection_config", + ResolveDbtExecutable => "resolve_dbt_executable", + ResolveDbtExecutionInvocation => "resolve_dbt_execution_invocation", + ResolveDbtInteropPlan => "resolve_dbt_interop_plan", + ResolveDbtRefReferences => "resolve_dbt_ref_references", + ResolveDbtReferenceRelation => "resolve_dbt_reference_relation", + ResolveDeferredTargetConfig => "resolve_deferred_target_config", + ResolveEffectiveAdapterName => "resolve_effective_adapter_name", + ResolveEffectiveCollectionRendering => "resolve_effective_collection_rendering", + ResolveEffectiveRunScope => "resolve_effective_run_scope", + ResolveEffectiveScenarioConfig => "resolve_effective_scenario_config", + ResolveEffectiveTimestampGrain => "resolve_effective_timestamp_grain", + ResolveExternalSqlReferenceResolver => "resolve_external_sql_reference_resolver", + ResolveFunctionSql => "resolve_function_sql", + ResolveFutureCursorSafety => "resolve_future_cursor_safety", + ResolveGraphSelection => "resolve_graph_selection", + ResolveLineageReference => "resolve_lineage_reference", + ResolveMicrobatchLookback => "resolve_microbatch_lookback", + ResolveModelActions => "resolve_model_actions", + ResolveModelDeferralInputs => "resolve_model_deferral_inputs", + ResolveModelFullRefresh => "resolve_model_full_refresh", + ResolveModelHookEntries => "resolve_model_hook_entries", + ResolveModelPlanAction => "resolve_model_plan_action", + ResolveModelSql => "resolve_model_sql", + ResolvePlannerScopes => "resolve_planner_scopes", + ResolveProjectConnectionConfig => "resolve_project_connection_config", + ResolveProjectSelectorTokens => "resolve_project_selector_tokens", + ResolveProjectSelectors => "resolve_project_selectors", + ResolvePythonCheckExcludes => "resolve_python_check_excludes", + ResolveRawDbtProfileConnection => "resolve_raw_dbt_profile_connection", + ResolveRefReferences => "resolve_ref_references", + ResolveRuleIgnoreSelectors => "resolve_rule_ignore_selectors", + ResolveSchemaActions => "resolve_schema_actions", + ResolveSkippedViewChain => "resolve_skipped_view_chain", + ResolveSourceReferences => "resolve_source_references", + ResolveSqlTestModelChains => "resolve_sql_test_model_chains", + ResolveSqlbuildExecutionPlanOutput => "resolve_sqlbuild_execution_plan_output", + ResolveSqlbuildModelSelectorNames => "resolve_sqlbuild_model_selector_names", + ResolveStartCursorConfig => "resolve_start_cursor_config", + ResolveStaticCommandScope => "resolve_static_command_scope", + ResolveStaticRelationContext => "resolve_static_relation_context", + ResolveTableFunctionReferences => "resolve_table_function_references", + ResolveTargetConfig => "resolve_target_config", + ResolveTargetConnectionConfig => "resolve_target_connection_config", + ResolveTargetName => "resolve_target_name", + ResolveUdfReferences => "resolve_udf_references", + RouteDbtInteropArgs => "route_dbt_interop_args", + RuleSelected => "rule_selected", + RulesBulkCachePath => "rules_bulk_cache_path", + RunAuditCommand => "run_audit_command", + RunBuildCommand => "run_build_command", + RunCheckCommand => "run_check_command", + RunCloneCommand => "run_clone_command", + RunClonePipelineWithOptions => "run_clone_pipeline_with_options", + RunClonePlanning => "run_clone_planning", + RunCompile => "run_compile", + RunCompileCommand => "run_compile_command", + RunCompileOnlyPipeline => "run_compile_only_pipeline", + RunCompilePipeline => "run_compile_pipeline", + RunContractCommand => "run_contract_command", + RunCostCommand => "run_cost_command", + RunDagCommand => "run_dag_command", + RunDbtCommand => "run_dbt_command", + RunDbtInitCommand => "run_dbt_init_command", + RunDbtSqlbuildWork => "run_dbt_sqlbuild_work", + RunDebugCommand => "run_debug_command", + RunDiffCommand => "run_diff_command", + RunDiffPipeline => "run_diff_pipeline", + RunFormatCommand => "run_format_command", + RunFreshnessCommand => "run_freshness_command", + RunInitCommand => "run_init_command", + RunJanitorCommand => "run_janitor_command", + RunLineageCommand => "run_lineage_command", + RunLint => "run_lint", + RunLoadCommand => "run_load_command", + RunPlanCommand => "run_plan_command", + RunPlannedSqlRules => "run_planned_sql_rules", + RunPlaygroundCommand => "run_playground_command", + RunQueryCommand => "run_query_command", + RunRefactorCommand => "run_refactor_command", + RunRules => "run_rules", + RunRulesCommand => "run_rules_command", + RunScenarioCaptureCommand => "run_scenario_capture_command", + RunScenarioTestCommand => "run_scenario_test_command", + RunScopeCommand => "run_scope_command", + RunSeedCommand => "run_seed_command", + RunSkillsCommand => "run_skills_command", + RunTestCommand => "run_test_command", + RuntimeCursorProducerNames => "runtime_cursor_producer_names", + SafeFormatFiles => "safe_format_files", + ScanHeaders => "scan_headers", + SchemaValidations => "schema_validations", + ScopeMetadataProjection => "scope_metadata_projection", + SelectCatalogueRules => "select_catalogue_rules", + SelectLatestFingerprints => "select_latest_fingerprints", + SelectProjectGraph => "select_project_graph", + SelectedRuleCodes => "selected_rule_codes", + SelectedSqlRuleCodes => "selected_sql_rule_codes", + SessionOutcomeAnalysis => "session_outcome_analysis", + SettingHelp => "setting_help", + SharedTransientLineCoordinator => "shared_transient_line_coordinator", + SourceBindingDiagnostics => "source_binding_diagnostics", + SourceFreshnessInputError => "SourceFreshnessInputError", + SourceFreshnessObservationError => "SourceFreshnessObservationError", + SplitProjectUnitTestSelectors => "split_project_unit_test_selectors", + SplitSelectorExpansion => "split_selector_expansion", + SplitUnitTestSelectors => "split_unit_test_selectors", + SqlBodyRanges => "sql_body_ranges", + SqlCodeScanner => "SqlCodeScanner", + SqlFileRuleKey => "sql_file_rule_key", + SqlLintPreparation => "sql_lint_preparation", + SqlModelRuleKeys => "sql_model_rule_keys", + SqlRefKey => "sql_ref_key", + SqlRuleLintKey => "sql_rule_lint_key", + SqlTestModelChainNamesByKey => "sql_test_model_chain_names_by_key", + SqlTestScanCache => "SqlTestScanCache", + SqlValueValidationError => "SqlValueValidationError", + StandalonePivotModels => "standalone_pivot_models", + StartModelAnalysisSession => "start_model_analysis_session", + StaticSqlTestPlanningDiagnostics => "static_sql_test_planning_diagnostics", + StoredMigrationFingerprint => "stored_migration_fingerprint", + StoredOldNameViewColumns => "stored_old_name_view_columns", + TerminalEventIndex => "TerminalEventIndex", + TerminalEventIndexContext => "_terminal_event_index_context", + TerminalEventIndexScopeFunction => "terminal_event_index_scope", + TopologicallyOrderKeys => "topologically_order_keys", + TransientStatusReporter => "TransientStatusReporter", + TransitiveClosureMany => "transitive_closure_many", + TypeCursorOverrides => "type_cursor_overrides", + UnreadableCursorInputsError => "unreadable_cursor_inputs_error", + UpdateSqlbuildSkills => "update_sqlbuild_skills", + UpstreamLoaderDependencyNames => "upstream_loader_dependency_names", + ValidRulesConfig => "valid_rules_config", + ValidateAuthoredSqlTestCtes => "validate_authored_sql_test_ctes", + ValidateCappedWatermarkInputs => "validate_capped_watermark_inputs", + ValidateCliWarehouse => "validate_cli_warehouse", + ValidateCompileDbtModelNames => "validate_compile_dbt_model_names", + ValidateCompileDbtModelReference => "validate_compile_dbt_model_reference", + ValidateDbtInitProject => "validate_dbt_init_project", + ValidateHookSqlSyntax => "validate_hook_sql_syntax", + ValidateLocalSqlAnalysis => "validate_local_sql_analysis", + ValidatePreviewTarget => "validate_preview_target", + ValidateResourceIdentity => "validate_resource_identity", + ValidateSourceCursorInputColumns => "validate_source_cursor_input_columns", + ValidateSqlSyntax => "validate_sql_syntax", + WithBindingDiagnostics => "with_binding_diagnostics", + WithDeclaredSourceReads => "with_declared_source_reads", + WithDuplicateLiteralHints => "with_duplicate_literal_hints", + WithHonestModelWriteHashes => "with_honest_model_write_hashes", + WithMigrationFingerprints => "with_migration_fingerprints", + WithPlanMetadata => "with_plan_metadata", + WithPlanWarnings => "with_plan_warnings", + WithStoragePolicies => "with_storage_policies", + WithTypeRecovery => "with_type_recovery", + WithoutDeclaredColumnsHash => "without_declared_columns_hash", + WithoutDestinationCursor => "without_destination_cursor", + WriteAtomically => "write_atomically", + WriteCheckResults => "write_check_results", + WriteCompileTarget => "write_compile_target", + WriteCostRun => "write_cost_run", + WriteDbtExecutionPlanText => "write_dbt_execution_plan_text", + WriteDbtExecutionSummary => "write_dbt_execution_summary", + WriteExecutionHeader => "write_execution_header", + WriteExecutionJsonOutput => "write_execution_json_output", + WriteReusableCompile => "write_reusable_compile", + WriteRuntimeTarget => "write_runtime_target", + WriteSourceFreshnessRecords => "write_source_freshness_records", + WriteSqlRuleCache => "write_sql_rule_cache", + WriteSqlbuildSkipNotice => "write_sqlbuild_skip_notice", + YamlEntryLine => "yaml_entry_line", + } +} + +/// Any registered native name, for helpers that accept records, errors and module attributes. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum NativeItem { + Record(RecordId), + Error(ErrorId), + Attr(NativeAttr), +} + +impl RecordId { + /// The record registered under `name`, for class names a pure-Rust crate chose. + pub(crate) fn from_name(name: &str) -> Option { + Self::ALL.iter().copied().find(|id| id.name() == name) + } +} + +impl NativeAttr { + /// The attribute registered under `name`, for function names a pure-Rust crate chose. + pub(crate) fn from_name(name: &str) -> Option { + Self::ALL.iter().copied().find(|id| id.name() == name) + } +} + +impl From for NativeItem { + fn from(id: RecordId) -> Self { + Self::Record(id) + } +} + +impl From for NativeItem { + fn from(id: ErrorId) -> Self { + Self::Error(id) + } +} + +impl From for NativeItem { + fn from(id: NativeAttr) -> Self { + Self::Attr(id) + } +} diff --git a/crates/sqlbuild-python/src/bindings/_helpers/records/mod.rs b/crates/sqlbuild-python/src/bindings/_helpers/records/mod.rs index 2bc36c8a91..7d5d3b8228 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/records/mod.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/records/mod.rs @@ -1,6 +1,8 @@ pub(crate) mod constants; pub(crate) mod error_classes; pub(crate) mod fields; +pub(crate) mod ids; pub(crate) mod models; pub(crate) mod registration; +pub(crate) mod registry; pub(crate) mod types; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/records/models.rs b/crates/sqlbuild-python/src/bindings/_helpers/records/models.rs index 4c2b8657de..19c80e379a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/records/models.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/records/models.rs @@ -8,10 +8,12 @@ use pyo3::pyclass::CompareOp; use pyo3::types::{PyDict, PyTuple, PyType}; use pyo3::{IntoPyObjectExt, pyclass, pymethods}; +use crate::bindings::_helpers::native_host::models::HostId; use crate::bindings::_helpers::records::fields::{ build_values, cached_value, compared_values, frozen_error, ordered_comparison, record_spec, repr_text, store_cached, }; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::types::{ClassMethod, ClassProperty, RecordFactory}; /// How a record field is filled when the caller does not pass it. @@ -32,8 +34,14 @@ pub(crate) enum FieldDefault { EmptyFrozenset, /// `getattr(getattr(import(module), class), member)`. Member(&'static str, &'static str, &'static str), - /// `getattr(import(module), name)()`, evaluated for every instance. - Call(&'static str, &'static str), + /// A member of a native string enum. + NativeMember(RecordId, &'static str), + /// A member of a staying-Python enum reached through the host. + HostMember(HostId, &'static str), + /// A native record class called with no arguments, evaluated for every instance. + NativeCall(RecordId), + /// A native Rust class called with no arguments, evaluated for every instance. + NativeClassCall(NativeAttr), /// A native factory, evaluated for every instance. Factory(RecordFactory), /// `getattr(import(module), name)(text)`, evaluated for every instance. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/records/registry.rs b/crates/sqlbuild-python/src/bindings/_helpers/records/registry.rs new file mode 100644 index 0000000000..abc0274b79 --- /dev/null +++ b/crates/sqlbuild-python/src/bindings/_helpers/records/registry.rs @@ -0,0 +1,165 @@ +//! Typed handles to what `sqlbuild._native` registers, checked against the registration tables at init. + +use std::collections::BTreeSet; + +use pyo3::exceptions::{PyAttributeError, PyImportError}; +use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyModule, PyResult, Python}; +use pyo3::sync::PyOnceLock; +use pyo3::types::PyString; + +use crate::bindings::_helpers::records::constants::{ENUMS, ERRORS, NATIVE_MODULE}; +use crate::bindings::_helpers::records::fields::all_specs; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, NativeItem, RecordId}; + +/// The extension module, imported once for attribute lookups. +static MODULE: PyOnceLock> = PyOnceLock::new(); +/// Each `RecordId`'s interned attribute name, by variant position. +static CLASS_NAMES: PyOnceLock>> = PyOnceLock::new(); +/// Each `ErrorId`'s interned attribute name, by variant position. +static ERROR_NAMES: PyOnceLock>> = PyOnceLock::new(); +/// Each `NativeAttr`'s interned attribute name, by variant position. +static ATTRIBUTE_NAMES: PyOnceLock>> = PyOnceLock::new(); + +fn module(py: Python<'_>) -> PyResult<&Py> { + MODULE.get_or_try_init(py, || Ok(py.import(NATIVE_MODULE)?.unbind())) +} + +/// The module attribute at `index` of `names`, interning the names on first use. +fn attribute<'py, 'a>( + py: Python<'py>, + names: &PyOnceLock>>, + all: impl Iterator, + index: usize, +) -> PyResult> { + let names: &Vec> = names.get_or_init(py, || { + all.map(|name| PyString::intern(py, name).unbind()) + .collect() + }); + module(py)?.bind(py).getattr(names[index].bind(py)) +} + +/// Names that are in exactly one of two name sets, sorted. +fn mismatched(left: BTreeSet<&str>, right: BTreeSet<&str>) -> Vec { + left.symmetric_difference(&right) + .map(|name| (*name).to_owned()) + .collect() +} + +/// Fail loudly when the typed names and the registration tables disagree. +fn check_tables() -> PyResult<()> { + let records: BTreeSet<&str> = all_specs() + .map(|spec| spec.name) + .chain( + ENUMS + .iter() + .flat_map(|group| group.iter()) + .map(|spec| spec.name), + ) + .collect(); + let errors: BTreeSet<&str> = ERRORS + .iter() + .flat_map(|group| group.iter()) + .map(|spec| spec.name) + .collect(); + let mut problems: Vec = + mismatched(records, RecordId::ALL.iter().map(|id| id.name()).collect()); + problems.extend(mismatched( + errors, + ErrorId::ALL.iter().map(|id| id.name()).collect(), + )); + if problems.is_empty() { + return Ok(()); + } + Err(PyImportError::new_err(format!( + "native registry and registration tables disagree: {}", + problems.join(", ") + ))) +} + +/// Fail module init when the typed names and the registration tables disagree. +pub(crate) fn verify_tables() -> PyResult<()> { + check_tables() +} + +/// Fail module init when a `NativeAttr` names an attribute the finished module lacks. +pub(crate) fn verify_attributes(module: &Bound<'_, PyModule>) -> PyResult<()> { + let mut missing: Vec<&str> = Vec::new(); + for id in NativeAttr::ALL { + if !module.hasattr(id.name())? { + missing.push(id.name()); + } + } + if missing.is_empty() { + return Ok(()); + } + Err(PyImportError::new_err(format!( + "the native module does not register: {}", + missing.join(", ") + ))) +} + +/// The native record class or string enum `id`, read now so a replaced attribute is seen. +pub(crate) fn class(py: Python<'_>, id: RecordId) -> PyResult> { + attribute( + py, + &CLASS_NAMES, + RecordId::ALL.iter().map(|id| id.name()), + id as usize, + ) +} + +/// The native error class `id`, read now so a replaced attribute is seen. +pub(crate) fn error(py: Python<'_>, id: ErrorId) -> PyResult> { + attribute( + py, + &ERROR_NAMES, + ErrorId::ALL.iter().map(|id| id.name()), + id as usize, + ) +} + +/// The module attribute `id`, read now so a replaced attribute is seen. +pub(crate) fn native(py: Python<'_>, id: NativeAttr) -> PyResult> { + attribute( + py, + &ATTRIBUTE_NAMES, + NativeAttr::ALL.iter().map(|id| id.name()), + id as usize, + ) +} + +/// A member or class attribute of the native record class or string enum `id`. +pub(crate) fn member<'py>( + py: Python<'py>, + id: RecordId, + name: &str, +) -> PyResult> { + class(py, id)?.getattr(name) +} + +/// Any registered native name. +pub(crate) fn item(py: Python<'_>, id: impl Into) -> PyResult> { + match id.into() { + NativeItem::Record(id) => class(py, id), + NativeItem::Error(id) => error(py, id), + NativeItem::Attr(id) => native(py, id), + } +} + +/// The native record class or string enum a pure-Rust crate names, failing as `getattr` would. +pub(crate) fn record_named(name: &str) -> PyResult { + RecordId::from_name(name).ok_or_else(|| { + PyAttributeError::new_err(format!( + "module 'sqlbuild._native' has no attribute '{name}'" + )) + }) +} + +/// The native module attribute a pure-Rust crate names, failing as `getattr` would. +pub(crate) fn attribute_named(name: &str) -> PyResult { + NativeAttr::from_name(name).ok_or_else(|| { + PyAttributeError::new_err(format!( + "module 'sqlbuild._native' has no attribute '{name}'" + )) + }) +} diff --git a/crates/sqlbuild-python/src/bindings/_helpers/refactor/contract_compare.rs b/crates/sqlbuild-python/src/bindings/_helpers/refactor/contract_compare.rs index a8063b2f4d..1aeded45f9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/refactor/contract_compare.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/refactor/contract_compare.rs @@ -7,13 +7,13 @@ use pyo3::{pyfunction, wrap_pyfunction}; use sqlbuild_core::text::main::python_casefold::python_casefold; use crate::bindings::_helpers::boundary::keyword_request::Request; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::type_system::equality::types_equal as native_types_equal; -const ADOPTION_MODELS_MODULE: &str = "sqlbuild._native"; -const ADOPTION_TYPES_MODULE: &str = "sqlbuild._native"; -const ADAPTER_MODELS_MODULE: &str = "sqlbuild.adapter.contract.models"; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; -const DYNAMIC_FAMILIES_MODULE: &str = "sqlbuild._native"; const DATACLASSES_MODULE: &str = "dataclasses"; const MODEL_RESOURCE: &str = "model"; const SOURCE_RESOURCE: &str = "source"; @@ -28,12 +28,8 @@ struct Vocabulary<'py> { impl<'py> Vocabulary<'py> { fn new(py: Python<'py>) -> PyResult { Ok(Self { - finding: py - .import(ADOPTION_MODELS_MODULE)? - .getattr("ContractFinding")?, - kinds: py - .import(ADOPTION_TYPES_MODULE)? - .getattr("ContractFindingKind")?, + finding: registry::class(py, RecordId::ContractFinding)?, + kinds: registry::class(py, RecordId::ContractFindingKind)?, replace: py.import(DATACLASSES_MODULE)?.getattr("replace")?, }) } @@ -104,7 +100,7 @@ fn column_infos<'py>( py: Python<'py>, columns: &Bound<'py, PyAny>, ) -> PyResult> { - let column_type: Bound<'py, PyAny> = py.import(ADAPTER_MODELS_MODULE)?.getattr("ColumnInfo")?; + let column_type: Bound<'py, PyAny> = host::entry(py, AdapterHost::ColumnInfo)?; let mut infos: Vec> = Vec::new(); for column in columns.try_iter()? { let column: Bound<'py, PyAny> = column?; @@ -140,8 +136,7 @@ fn model_evidence<'py>(model: &Bound<'py, PyAny>) -> PyResult> ) }; keyword_call( - &py.import(ADOPTION_MODELS_MODULE)? - .getattr("ContractEvidence")?, + ®istry::class(py, RecordId::ContractEvidence)?, vec![ ("resource_type", resource_type(py, MODEL_RESOURCE)?), ("resource_name", model.getattr("name")?), @@ -189,8 +184,7 @@ fn source_evidence<'py>( text(py, &relation) }; keyword_call( - &py.import(ADOPTION_MODELS_MODULE)? - .getattr("ContractEvidence")?, + ®istry::class(py, RecordId::ContractEvidence)?, vec![ ("resource_type", resource), ("resource_name", name), @@ -258,8 +252,7 @@ fn undeclared_finding<'py>( let name: String = physical.getattr("name")?.extract()?; let physical_type: Bound<'py, PyAny> = physical.getattr("type")?; let matching: Bound<'py, PyAny> = keyword_call( - &py.import(DYNAMIC_FAMILIES_MODULE)? - .getattr("matching_dynamic_families")?, + ®istry::native(py, NativeAttr::MatchingDynamicFamilies)?, vec![ ("families", item.getattr("dynamic_columns")?), ("column_name", text(py, &name)), @@ -462,7 +455,7 @@ fn compare_contract_evidence<'py>( fn relation_info<'py>(item: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = item.py(); keyword_call( - &py.import(ADAPTER_MODELS_MODULE)?.getattr("RelationInfo")?, + &host::entry(py, AdapterHost::RelationInfo)?, vec![ ("database", item.getattr("database")?), ("schema", item.getattr("schema")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/refactor/contract_edits.rs b/crates/sqlbuild-python/src/bindings/_helpers/refactor/contract_edits.rs index 141dbbc21c..7d6ec9ae98 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/refactor/contract_edits.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/refactor/contract_edits.rs @@ -15,10 +15,9 @@ use sqlbuild_refactor::refactoring::models::{ use sqlbuild_refactor::refactoring::types::PhysicalColumn; use crate::bindings::_helpers::boundary::panics::compiler_guard; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; -const ADOPTION_ERRORS_MODULE: &str = "sqlbuild._native"; -const ADOPTION_MODELS_MODULE: &str = "sqlbuild._native"; -const ADOPTION_TYPES_MODULE: &str = "sqlbuild._native"; /// Finding kinds an edit resolves without overwrite, then the ones overwrite also resolves. const RESOLVED_KINDS: ([&str; 1], [&str; 2]) = ( ["missing_declaration"], @@ -40,11 +39,7 @@ fn edit_row(result: Result) -> PyResult { /// `ContractAdoptionError(message)`. fn adoption_error(py: Python<'_>, message: String) -> PyErr { - match py - .import(ADOPTION_ERRORS_MODULE) - .and_then(|module| module.getattr("ContractAdoptionError")) - .and_then(|class| class.call1((message,))) - { + match registry::error(py, ErrorId::ContractAdoption).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } @@ -175,15 +170,11 @@ fn remaining_contract_findings<'py>( keywords.set_item("resource_name", first.getattr("resource_name")?)?; keywords.set_item( "kind", - py.import(ADOPTION_TYPES_MODULE)? - .getattr("ContractFindingKind")? - .getattr("OWNERSHIP_CONFLICT")?, + registry::class(py, RecordId::ContractFindingKind)?.getattr("OWNERSHIP_CONFLICT")?, )?; keywords.set_item("message", conflict)?; - let ownership: Bound<'py, PyAny> = py - .import(ADOPTION_MODELS_MODULE)? - .getattr("ContractFinding")? - .call((), Some(&keywords))?; + let ownership: Bound<'py, PyAny> = + registry::class(py, RecordId::ContractFinding)?.call((), Some(&keywords))?; let mut remaining: Vec> = findings.clone(); remaining.push(ownership); return PyTuple::new(py, remaining); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/refactor/offline_compile.rs b/crates/sqlbuild-python/src/bindings/_helpers/refactor/offline_compile.rs index 24d25ddda6..100c1e2f82 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/refactor/offline_compile.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/refactor/offline_compile.rs @@ -2,25 +2,15 @@ use std::path::Path; -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class_at; use pyo3::IntoPyObject; +use pyo3::exceptions::PyValueError; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python}; use pyo3::types::{PyBool, PyString}; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS: &str = "sqlbuild._native"; -const COMPILE_TYPES: &str = "sqlbuild.compiler.compile.types"; -const COMPILE_ERRORS: &str = "sqlbuild._native"; -const PLANNER_ERRORS: &str = "sqlbuild._native"; -const RULES_ERRORS: &str = "sqlbuild._native"; -const SPEC_ERRORS: &str = "sqlbuild._native"; -const ENTRY_MODELS: &str = "sqlbuild._native"; -const COMMAND_TYPES: &str = "sqlbuild._native"; -const REFACTOR_MODELS: &str = "sqlbuild._native"; const DEFAULT_ERROR_CODE: &str = "E001"; /// One offline compile's project facts, or `None`, and every error that stopped or followed it. @@ -29,13 +19,13 @@ pub(crate) struct OfflineCompile<'py> { pub(crate) errors: Vec>, } -fn enum_member<'py>(py: Python<'py>, class: &str, member: &str) -> PyResult> { - shape_class_at(py, COMPILE_TYPES, class)?.getattr(member) +fn enum_member<'py>(py: Python<'py>, class: RecordId, member: &str) -> PyResult> { + registry::member(py, class, member) } -fn matches_any_class(py: Python<'_>, error: &PyErr, classes: &[(&str, &str)]) -> PyResult { - for (module, name) in classes { - if error.value(py).is_instance(&imported(py, module, name)?)? { +fn matches_any_class(py: Python<'_>, error: &PyErr, classes: &[ErrorId]) -> PyResult { + for class in classes { + if error.value(py).is_instance(®istry::error(py, *class)?)? { return Ok(true); } } @@ -56,10 +46,16 @@ fn compile_error_diagnostic<'py>(py: Python<'py>, error: &PyErr) -> PyResult(py: Python<'py>, error: &PyErr) -> PyResult(py: Python<'py>, error: &PyErr) -> PyResult> { let value: Bound<'py, PyAny> = error.value(py).clone().into_any(); let location: Bound<'py, PyAny> = - if value.is_instance(&imported(py, COMPILE_ERRORS, "SqlTestReferenceError")?)? { + if value.is_instance(®istry::error(py, ErrorId::SqlTestReference)?)? { value.getattr("location")? } else { py.None().into_bound(py) }; call_with( - &imported(py, COMPILE_MODELS, "CompilerDiagnostic")?, + ®istry::class(py, RecordId::CompilerDiagnostic)?, &[ - ("phase", enum_member(py, "DiagnosticPhase", "TEST")?), - ("severity", enum_member(py, "DiagnosticSeverity", "ERROR")?), + ("phase", enum_member(py, RecordId::DiagnosticPhase, "TEST")?), + ( + "severity", + enum_member(py, RecordId::DiagnosticSeverity, "ERROR")?, + ), ("code", value.getattr("code")?), ("message", value.str()?.into_any()), ("help", value.getattr("help")?), @@ -105,11 +104,11 @@ fn analyzed<'py>( no_cache: bool, ) -> PyResult> { let selection: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "CompileAnalysisSelection")?, + ®istry::class(py, RecordId::CompileAnalysisSelection)?, &[("no_cache", PyBool::new(py, no_cache).to_owned().into_any())], )?; call_with( - &imported(py, NATIVE_MODULE, "analyze_compile_project")?, + ®istry::native(py, NativeAttr::AnalyzeCompileProject)?, &[ ("project_dir", project_dir.into_pyobject(py)?.into_any()), ( @@ -119,12 +118,12 @@ fn analyzed<'py>( ("selected_target", py.None().into_bound(py)), ( "lineage_mode", - imported(py, COMMAND_TYPES, "CompileLineageMode")?.getattr("NONE")?, + registry::class(py, RecordId::CompileLineageMode)?.getattr("NONE")?, ), ("cli_vars", py.None().into_bound(py)), ( "profile_flags", - imported(py, ENTRY_MODELS, "CompileProfileFlags")?.call0()?, + registry::class(py, RecordId::CompileProfileFlags)?.call0()?, ), ("analysis_selection", selection), ("status", py.None().into_bound(py)), @@ -141,15 +140,8 @@ pub(crate) fn compile_for_refactor<'py>( let analysis: Bound<'py, PyAny> = match analyzed(py, project_dir, no_cache) { Ok(analysis) => analysis, Err(error) - if matches_any_class( - py, - &error, - &[ - ("builtins", "ValueError"), - (RULES_ERRORS, "RulesError"), - (SPEC_ERRORS, "SpecConfigError"), - ], - )? => + if error.is_instance_of::(py) + || matches_any_class(py, &error, &[ErrorId::Rules, ErrorId::SpecConfig])? => { return Ok(OfflineCompile { project: py.None().into_bound(py), @@ -161,7 +153,7 @@ pub(crate) fn compile_for_refactor<'py>( let mut errors: Vec> = error_diagnostics(analysis.getattr("diagnostics")?)?; if errors.is_empty() { let planned: PyResult> = call_with( - &imported(py, NATIVE_MODULE, "static_sql_test_planning_diagnostics")?, + ®istry::native(py, NativeAttr::StaticSqlTestPlanningDiagnostics)?, &[ ( "target_dir", @@ -177,10 +169,7 @@ pub(crate) fn compile_for_refactor<'py>( if matches_any_class( py, &error, - &[ - (COMPILE_ERRORS, "CompileInputError"), - (PLANNER_ERRORS, "PlannerInputError"), - ], + &[ErrorId::CompileInput, ErrorId::PlannerInput], )? => { errors.push(planning_error_diagnostic(py, &error)?); @@ -189,7 +178,7 @@ pub(crate) fn compile_for_refactor<'py>( } } let project: Bound<'py, PyAny> = call_with( - &imported(py, REFACTOR_MODELS, "RefactorProject")?, + ®istry::class(py, RecordId::RefactorProject)?, &[ ("project_dir", project_dir.into_pyobject(py)?.into_any()), ("graph", analysis.getattr("graph")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/refactor/python_strings.rs b/crates/sqlbuild-python/src/bindings/_helpers/refactor/python_strings.rs index b28bc6ecb7..1532b42d39 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/refactor/python_strings.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/refactor/python_strings.rs @@ -9,8 +9,9 @@ use sqlbuild_pysource::string_literals::main::string_literal_locations::string_l use sqlbuild_pysource::string_literals::models::StringQuery; use crate::bindings::_helpers::boundary::keyword_request::Request; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const REFACTORING_MODELS_MODULE: &str = "sqlbuild._native"; /// The discovered Python declarations whose files a refactoring searches. const PYTHON_DECLARATIONS: [&str; 8] = [ "loader_functions", @@ -68,9 +69,7 @@ fn find_python_string_locations<'py>( Some(context) => context.extract()?, None => None, }; - let location_type: Bound<'py, PyAny> = py - .import(REFACTORING_MODELS_MODULE)? - .getattr("ManualLocation")?; + let location_type: Bound<'py, PyAny> = registry::class(py, RecordId::ManualLocation)?; let version = py.version_info(); let mut locations: Vec> = Vec::new(); for (_, relative_path) in python_paths(&request.get("discovered")?)? { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/refactor/refactor_command.rs b/crates/sqlbuild-python/src/bindings/_helpers/refactor/refactor_command.rs index 854a7e8d8c..d1d5a10730 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/refactor/refactor_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/refactor/refactor_command.rs @@ -36,13 +36,13 @@ use crate::bindings::_helpers::boundary::panics::{NativeCompilerError, compiler_ use crate::bindings::_helpers::build_progress::transient_status::status_reporter; use crate::bindings::_helpers::lineage::lineage_index::lineage_key; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::refactor::offline_compile::{OfflineCompile, compile_for_refactor}; use crate::bindings::_helpers::refactor::refactor_facts::{ declaration_placement, model_facts, refactor_facts, }; -const ERRORS_MODULE: &str = "sqlbuild._native"; -const REFACTOR_ERRORS_MODULE: &str = "sqlbuild._native"; const STAGING_PREFIX: &str = "sqb-refactor-"; const MOVE_COMMAND: &str = "mv"; const EDIT_ERROR_CODE: &str = "C957"; @@ -58,7 +58,7 @@ fn user_error(py: Python<'_>, (message, code, help): (String, &str, Option = PyDict::new(py); options.set_item("code", code)?; options.set_item("help", help)?; - imported(py, ERRORS_MODULE, "CliUserError")?.call((message,), Some(&options)) + registry::error(py, ErrorId::CliUser)?.call((message,), Some(&options)) })(); match raised { Ok(error) => PyErr::from_value(error), @@ -68,10 +68,8 @@ fn user_error(py: Python<'_>, (message, code, help): (String, &str, Option, error: RefactorError) -> PyErr { - let class = |name: &str| -> PyErr { - match imported(py, REFACTOR_ERRORS_MODULE, name) - .and_then(|class| class.call1((error.message.clone(),))) - { + let class = |name: ErrorId| -> PyErr { + match registry::error(py, name).and_then(|class| class.call1((error.message.clone(),))) { Ok(raised) => PyErr::from_value(raised), Err(failure) => failure, } @@ -80,8 +78,8 @@ fn refactor_error(py: Python<'_>, error: RefactorError) -> PyErr { RefactorErrorKind::Input => user_error(py, (error.message, &error.code, error.help)), RefactorErrorKind::Edit => user_error(py, (error.message, EDIT_ERROR_CODE, None)), RefactorErrorKind::Write => user_error(py, (error.message, WRITE_ERROR_CODE, error.help)), - RefactorErrorKind::Value => class("RefactorValueError"), - RefactorErrorKind::Io => class("RefactorIOError"), + RefactorErrorKind::Value => class(ErrorId::RefactorValue), + RefactorErrorKind::Io => class(ErrorId::RefactorIO), RefactorErrorKind::Internal => NativeCompilerError::new_err(error.message), } } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/refactor/refactor_facts.rs b/crates/sqlbuild-python/src/bindings/_helpers/refactor/refactor_facts.rs index 4bc156e557..9440b2cd1d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/refactor/refactor_facts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/refactor/refactor_facts.rs @@ -11,14 +11,12 @@ use sqlbuild_refactor::refactoring::models::{ SchemaColumnFacts, SqlFileRole, }; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::scopes::scope_lookup::build_scope_lookup; use crate::bindings::_helpers::scopes::scope_reports::scope_move_preview; use crate::bindings::_helpers::scopes::scope_validation::scope_relocated_declarations; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const CONFIG_MODULE: &str = "sqlbuild._native"; -const SCOPE_MODELS: &str = "sqlbuild._native"; -const SCOPE_TYPES: &str = "sqlbuild._native"; const GENERIC_DIALECT: &str = "generic"; const MATERIALIZED_KEY: &str = "materialized"; const MIGRATE_FROM_KEY: &str = "migrate_from"; @@ -28,8 +26,8 @@ fn posix(path: &Bound<'_, PyAny>) -> PyResult { path.call_method0("as_posix")?.extract() } -fn native<'py>(py: Python<'py>, name: &str) -> PyResult> { - py.import(NATIVE_MODULE)?.getattr(name) +fn native<'py>(py: Python<'py>, name: NativeAttr) -> PyResult> { + registry::native(py, name) } fn discovered_files(files: &Bound<'_, PyAny>) -> PyResult> { @@ -123,9 +121,7 @@ fn model(model: &Bound<'_, PyAny>, expansion: Option<&Bound<'_, PyAny>>) -> PyRe let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("values", &values)?; options.set_item("key", MATERIALIZED_KEY)?; - let materialized: Option = py - .import(CONFIG_MODULE)? - .getattr("get_config_str")? + let materialized: Option = registry::native(py, NativeAttr::GetConfigStr)? .call((), Some(&options))? .extract()?; let migrate_from: Option> = values.get_item(MIGRATE_FROM_KEY)?; @@ -266,7 +262,7 @@ fn python_locations( )?; } let mut locations: Vec = Vec::new(); - for location in native(py, "find_python_string_locations")? + for location in native(py, NativeAttr::FindPythonStringLocations)? .call((), Some(&options))? .try_iter()? { @@ -335,15 +331,11 @@ pub(crate) fn declaration_placement( let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item( "kind", - py.import(SCOPE_TYPES)? - .getattr("ResourceKind")? - .getattr("MODEL")?, + registry::class(py, RecordId::ResourceKind)?.getattr("MODEL")?, )?; options.set_item("name", model)?; - let resource: Bound<'_, PyAny> = py - .import(SCOPE_MODELS)? - .getattr("ResourceIdentity")? - .call((), Some(&options))?; + let resource: Bound<'_, PyAny> = + registry::class(py, RecordId::ResourceIdentity)?.call((), Some(&options))?; let records: Option> = scope_relocated_declarations(py, &index, &resource, destination)?; if let Some(records) = records { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/render/authored_sql.rs b/crates/sqlbuild-python/src/bindings/_helpers/render/authored_sql.rs index 137c2a04a2..963b752559 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/render/authored_sql.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/render/authored_sql.rs @@ -10,6 +10,8 @@ use crate::bindings::_helpers::boundary::keyword_request::Request; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; use crate::bindings::_helpers::model_config::template_data::record_reads; use crate::bindings::_helpers::model_config::unicode_environment::unicode_environment; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::render::declaration_expansion::{ DeclarationExpansion, ExpandedDeclarations, ExpansionClasses, ExpansionConsumer, SpanRow, expand_declarations, span_rows, @@ -19,12 +21,6 @@ use crate::bindings::_helpers::render::macro_expansion_result::expand_sql_macros use crate::bindings::_helpers::sqltext::declaration_references::declaration_scan; use crate::bindings::_helpers::sqltext::static_sql::{InterpolationSources, interpolation_batch}; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; -const SCOPE_MODELS_MODULE: &str = "sqlbuild._native"; const FUNCTOOLS_MODULE: &str = "functools"; const DECLARATION_FIELDS: [&str; 4] = [ "enums", @@ -48,7 +44,7 @@ fn substituted<'py>( } else { Some(context_values.downcast()?.clone()) }, - imported(py, NATIVE_MODULE, "render_project_var_text")?, + registry::native(py, NativeAttr::RenderProjectVarText)?, ); let text: String = sql.extract()?; let path: String = file_path.str()?.extract()?; @@ -59,7 +55,7 @@ fn substituted<'py>( record_reads(py, reads.iter().map(|(kind, name)| (*kind, name.as_str())))?; if let Some(message) = error { return Err(PyErr::from_value( - imported(py, COMPILE_ERRORS_MODULE, "CompileInputError")?.call1((message,))?, + registry::error(py, ErrorId::CompileInput)?.call1((message,))?, )); } Ok((rendered.unwrap_or(text), spans)) @@ -67,7 +63,7 @@ fn substituted<'py>( /// `ExpansionSpan` objects for span rows, in order. fn span_objects<'py>(py: Python<'py>, spans: &[SpanRow]) -> PyResult> { - let span_class: Bound<'py, PyAny> = imported(py, NATIVE_RECORDS_MODULE, "ExpansionSpan")?; + let span_class: Bound<'py, PyAny> = registry::class(py, RecordId::ExpansionSpan)?; let objects: Vec> = spans .iter() .map(|span| span_class.call1(*span)) @@ -94,7 +90,7 @@ fn declaration_context<'py>( }) .collect::>()?; call_with( - &imported(py, COMPILE_MODELS_MODULE, "DeclarationResolutionContext")?, + ®istry::class(py, RecordId::DeclarationResolutionContext)?, &fields, ) } @@ -116,7 +112,7 @@ fn declarations_expanded<'py>( } let context: Bound<'py, PyAny> = declaration_context(py, declarations)?; let render_constant: Bound<'py, PyAny> = imported(py, FUNCTOOLS_MODULE, "partial")?.call( - (imported(py, NATIVE_MODULE, "render_constant_declaration")?,), + (registry::native(py, NativeAttr::RenderConstantDeclaration)?,), Some(&keywords( py, &[ @@ -127,8 +123,8 @@ fn declarations_expanded<'py>( )?), )?; let classes = ExpansionClasses { - usage_record: imported(py, SCOPE_MODELS_MODULE, "UsageRecord")?, - compile_error: imported(py, COMPILE_ERRORS_MODULE, "CompileInputError")?, + usage_record: registry::class(py, RecordId::UsageRecord)?, + compile_error: registry::error(py, ErrorId::CompileInput)?, }; let path: String = file_path.str()?.extract()?; let expanded: ExpandedDeclarations<'py> = expand_declarations(&DeclarationExpansion { @@ -213,7 +209,7 @@ fn path_declarations<'py>( ) -> PyResult> { let py: Python<'py> = context.py(); let resolution_class: Bound<'py, PyAny> = - imported(py, COMPILE_MODELS_MODULE, "DeclarationResolutionContext")?; + registry::class(py, RecordId::DeclarationResolutionContext)?; let resolver: Bound<'py, PyAny> = context.getattr("declaration_resolver")?; let declarations: Bound<'py, PyAny> = if resolver.is_none() { call_with( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/render/declaration_indexes.rs b/crates/sqlbuild-python/src/bindings/_helpers/render/declaration_indexes.rs index 2c74115fff..6bfd127f0e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/render/declaration_indexes.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/render/declaration_indexes.rs @@ -2,23 +2,19 @@ use std::collections::HashMap; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{ Bound, PyAny, PyAnyMethods, PyErr, PyModule, PyModuleMethods, PyResult, Python, }; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; -const COMPILE_INPUT_ERROR: &str = "CompileInputError"; const DATACLASSES_MODULE: &str = "dataclasses"; /// `CompileInputError(message)`. fn compile_input_error(py: Python<'_>, message: String) -> PyErr { - match py - .import(COMPILE_ERRORS_MODULE) - .and_then(|module| module.getattr(COMPILE_INPUT_ERROR)) - .and_then(|class| class.call1((message,))) - { + match registry::error(py, ErrorId::CompileInput).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/render/declaration_values.rs b/crates/sqlbuild-python/src/bindings/_helpers/render/declaration_values.rs index b2aca182b7..13b5c6e3a5 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/render/declaration_values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/render/declaration_values.rs @@ -7,21 +7,15 @@ use pyo3::types::{PyDict, PyDictMethods, PyFrozenSet, PyInt, PyList, PyString, P use pyo3::{IntoPyObject, pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::scopes::scope_lookup::build_scope_lookup; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; -const SCOPE_MODELS_MODULE: &str = "sqlbuild._native"; -const SCOPE_TYPES_MODULE: &str = "sqlbuild._native"; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const DISCOVERY_MODELS_MODULE: &str = "sqlbuild._native"; -const SCOPE_ERRORS_MODULE: &str = "sqlbuild._native"; const NO_DECLARATION_KIND: u8 = 255; /// Declaration kinds by their native code: enum 0, constant 1, macro 2. const DECLARATION_KINDS: [&str; 3] = ["ENUM", "CONSTANT", "MACRO"]; -const SQL_VALUE_ERRORS_MODULE: &str = "sqlbuild.sql_values.exceptions"; const TYPES_MODULE: &str = "types"; const SCALAR_KINDS: [&str; 6] = ["string", "integer", "boolean", "float", "decimal", "null"]; const COLLECTION_KINDS: [&str; 2] = ["list", "set"]; @@ -50,8 +44,8 @@ fn runtime_declarations<'py>( ) -> PyResult> { let py: Python<'py> = discovered.py(); let classes = ( - imported(py, SCOPE_MODELS_MODULE, "DeclarationIdentity")?, - imported(py, SCOPE_TYPES_MODULE, "DeclarationKind")?, + registry::class(py, RecordId::DeclarationIdentity)?, + registry::class(py, RecordId::DeclarationKind)?, ); let declarations: Bound<'py, PyDict> = PyDict::new(py); for (files, kind) in [("enum_files", "ENUM"), ("constant_files", "CONSTANT")] { @@ -64,10 +58,9 @@ fn runtime_declarations<'py>( } } } - let resource_identity: Bound<'py, PyAny> = - imported(py, SCOPE_MODELS_MODULE, "ResourceIdentity")?; + let resource_identity: Bound<'py, PyAny> = registry::class(py, RecordId::ResourceIdentity)?; let model_kind: Bound<'py, PyAny> = - imported(py, SCOPE_TYPES_MODULE, "ResourceKind")?.getattr("MODEL")?; + registry::class(py, RecordId::ResourceKind)?.getattr("MODEL")?; for model_file in discovered.getattr("model_files")?.try_iter()? { let model_file: Bound<'py, PyAny> = model_file?; let name: Bound<'py, PyAny> = model_file @@ -110,7 +103,7 @@ fn runtime_declarations<'py>( fn resource_specific<'py>(scope_index: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = scope_index.py(); let private: Bound<'py, PyAny> = - imported(py, SCOPE_TYPES_MODULE, "ScopeKind")?.getattr("PRIVATE")?; + registry::class(py, RecordId::ScopeKind)?.getattr("PRIVATE")?; let mut resources: Vec> = Vec::new(); for declaration in scope_index.getattr("declarations")?.try_iter()? { let declaration: Bound<'py, PyAny> = declaration?; @@ -139,7 +132,7 @@ pub(crate) fn declaration_scope_resolver<'py>( lookup.clone() }; let projection: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DeclarationRuntimeProjection")?, + ®istry::class(py, RecordId::DeclarationRuntimeProjection)?, &[( "declarations", imported(py, TYPES_MODULE, "MappingProxyType")?.call1((&declarations,))?, @@ -148,7 +141,7 @@ pub(crate) fn declaration_scope_resolver<'py>( let native_contexts: Bound<'py, PyAny> = native_declaration_contexts(&lookup, declarations.as_any())?; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "DeclarationScopeResolver")?, + ®istry::class(py, RecordId::DeclarationScopeResolver)?, &[ ("project_dir", discovered.getattr("project_dir")?), ("lookup", lookup), @@ -204,15 +197,15 @@ pub(crate) fn native_declaration_contexts<'py>( declarations: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = lookup.py(); - let kind_type: Bound<'py, PyAny> = imported(py, SCOPE_TYPES_MODULE, "DeclarationKind")?; + let kind_type: Bound<'py, PyAny> = registry::class(py, RecordId::DeclarationKind)?; let kinds: Vec> = DECLARATION_KINDS .iter() .map(|kind| kind_type.getattr(*kind)) .collect::>()?; let value_classes: Vec> = vec![ - imported(py, DISCOVERY_MODELS_MODULE, "EnumDeclaration")?, - imported(py, DISCOVERY_MODELS_MODULE, "ConstantDeclaration")?, - imported(py, COMPILE_MODELS_MODULE, "LoadedMacro")?, + registry::class(py, RecordId::EnumDeclaration)?, + registry::class(py, RecordId::ConstantDeclaration)?, + registry::class(py, RecordId::LoadedMacro)?, ]; let index: Bound<'py, PyAny> = lookup.getattr("visibility_index")?; let lookup_declarations: Bound<'py, PyAny> = lookup.getattr("declarations")?; @@ -241,18 +234,18 @@ pub(crate) fn native_declaration_contexts<'py>( )?); } let reasons: Bound<'py, PyDict> = PyDict::new(py); - for reason in imported(py, SCOPE_TYPES_MODULE, "VisibilityReason")?.try_iter()? { + for reason in registry::class(py, RecordId::VisibilityReason)?.try_iter()? { let reason: Bound<'py, PyAny> = reason?; reasons.set_item(reason.getattr("value")?, reason)?; } let classes: Bound<'py, PyDict> = PyDict::new(py); classes.set_item( "visibility_record", - imported(py, SCOPE_MODELS_MODULE, "VisibilityRecord")?, + registry::class(py, RecordId::VisibilityRecord)?, )?; classes.set_item( "resolution_context", - imported(py, COMPILE_MODELS_MODULE, "DeclarationResolutionContext")?, + registry::class(py, RecordId::DeclarationResolutionContext)?, )?; classes.set_item("reasons", reasons)?; let keys: Bound<'py, PyDict> = PyDict::new(py); @@ -268,29 +261,29 @@ pub(crate) fn native_declaration_contexts<'py>( lookup.getattr("index")?.getattr("declarations")?, )?; for (name, class) in [ - ("resource_record", "ResourceRecord"), - ("ownership_root", "OwnershipRoot"), - ("resource_identity", "ResourceIdentity"), - ("declaration_identity", "DeclarationIdentity"), + ("resource_record", RecordId::ResourceRecord), + ("ownership_root", RecordId::OwnershipRoot), + ("resource_identity", RecordId::ResourceIdentity), + ("declaration_identity", RecordId::DeclarationIdentity), ] { - lookups.set_item(name, imported(py, SCOPE_MODELS_MODULE, class)?)?; + lookups.set_item(name, registry::class(py, class)?)?; } lookups.set_item( "resource_kind", - imported(py, SCOPE_TYPES_MODULE, "ResourceKind")?, + registry::class(py, RecordId::ResourceKind)?, )?; lookups.set_item("declaration_kind", kind_type)?; lookups.set_item( "private_scope", - imported(py, SCOPE_TYPES_MODULE, "ScopeKind")?.getattr("PRIVATE")?, + registry::class(py, RecordId::ScopeKind)?.getattr("PRIVATE")?, )?; lookups.set_item( "identity_error", - imported(py, SCOPE_ERRORS_MODULE, "InvalidQualifiedIdentityError")?, + registry::error(py, ErrorId::InvalidQualifiedIdentity)?, )?; lookups.set_item( "path_error", - imported(py, SCOPE_ERRORS_MODULE, "InvalidScopePathError")?, + registry::error(py, ErrorId::InvalidScopePath)?, )?; let global_positions: Bound<'py, PyList> = PyList::new( py, @@ -299,7 +292,7 @@ pub(crate) fn native_declaration_contexts<'py>( .try_iter()? .collect::>>()?, )?; - imported(py, NATIVE_MODULE, "NativeDeclarationContexts")?.call1(( + registry::native(py, NativeAttr::NativeDeclarationContexts)?.call1(( PyList::new(py, positions)?, ( global_positions, @@ -365,7 +358,7 @@ fn rendered_constant<'py>( declaration.getattr("name")?.str()? ); return Err(PyErr::from_value( - imported(declaration.py(), NATIVE_MODULE, "SqlValueValidationError")? + registry::native(declaration.py(), NativeAttr::SqlValueValidationError)? .call1((format!( "{context} rendered value is {size} bytes; maximum is {MAX_SQL_VALUE_SIZE} bytes" ),))?, @@ -391,10 +384,9 @@ pub(crate) fn constant_declaration_sql<'py>( Ok(rendered) => return Ok(rendered), Err(error) => error, }; - let rendering_error: Bound<'py, PyAny> = - imported(py, SQL_VALUE_ERRORS_MODULE, "SqlValueRenderingError")?; + let rendering_error: Bound<'py, PyAny> = host::entry(py, ExecutorHost::SqlValueRenderingError)?; let validation_error: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "SqlValueValidationError")?; + registry::native(py, NativeAttr::SqlValueValidationError)?; let raised: Bound<'py, PyAny> = error.value(py).clone().into_any(); if !(raised.is_instance(&rendering_error)? || raised.is_instance(&validation_error)?) { return Err(error); @@ -405,7 +397,7 @@ pub(crate) fn constant_declaration_sql<'py>( _ => relative_path.clone(), }; let compile_error: Bound<'py, PyAny> = - imported(py, COMPILE_ERRORS_MODULE, "CompileInputError")?.call1((format!( + registry::error(py, ErrorId::CompileInput)?.call1((format!( "{} constant '{}' could not be rendered in '{}' by adapter '{}' as {}: {}", relative_path.str()?, declaration.getattr("name")?.str()?, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/render/macro_calls.rs b/crates/sqlbuild-python/src/bindings/_helpers/render/macro_calls.rs index 208e75a836..30dd1f7d1f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/render/macro_calls.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/render/macro_calls.rs @@ -10,13 +10,13 @@ use sqlbuild_render::macro_calls::main::splice_macro_calls::splice_macro_calls a use sqlbuild_render::macro_calls::models::{MacroCallScan, MacroScanFailure}; use crate::bindings::_helpers::boundary::panics::compiler_error; -use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::render::macro_call_memo::MacroCallMemo; use crate::bindings::_helpers::render::macro_call_rows::{SiteRow, SpanRow, site_row, span_row}; use crate::bindings::_helpers::render::macro_expansion::raised; use crate::bindings::types::CompilerDetach; -const COMPILE_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; const DECLARATION_CALLS: [&str; 2] = ["enum", "const"]; /// `(call start or None, call name or None, message)` of the error Python's scan raises. @@ -60,7 +60,7 @@ pub(crate) fn top_level_macro_call_names<'py>( .collect::>(), ), Err(error) => Err(raised( - &imported(py, COMPILE_EXCEPTIONS_MODULE, "CompileInputError")?, + ®istry::error(py, ErrorId::CompileInput)?, error.message(), )), } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/render/macro_expansion_result.rs b/crates/sqlbuild-python/src/bindings/_helpers/render/macro_expansion_result.rs index 230858dbbf..ee59c533ca 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/render/macro_expansion_result.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/render/macro_expansion_result.rs @@ -9,6 +9,10 @@ use pyo3::{pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, keywords, }; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::render::declaration_values::resolved_declaration_context; use crate::bindings::_helpers::render::macro_call_rows::span_row; use crate::bindings::_helpers::render::macro_expansion::{ @@ -16,23 +20,11 @@ use crate::bindings::_helpers::render::macro_expansion::{ }; use crate::bindings::_helpers::sqltext::sql_references::extract_references; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; -const COMPILE_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; -const FRONTIER_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; -const SCOPES_MODELS_MODULE: &str = "sqlbuild._native"; -const SCOPES_TYPES_MODULE: &str = "sqlbuild._native"; -const REF_MODELS_MODULE: &str = "sqlbuild.python_nodes.models"; -const REF_TYPES_MODULE: &str = "sqlbuild.python_nodes.types"; const TYPED_REFERENCE_FUNCTIONS: [(&str, &str); 3] = [ ("model", "__ref"), ("source", "__source"), ("seed", "__seed"), ]; -const USER_MACROS_MODULE: &str = "sqlbuild.compiler.compile._helpers.render.user_macros"; -const ACTIVE_BRIDGE_MODULE: &str = "sqlbuild.compiler.macro_bridge.main.active_macro_bridge"; /// The call prefix of every reference function, in `SqlReferenceKind` order. const GENERATED_REFERENCE_MARKERS: [&str; 6] = [ "__ref(", @@ -42,9 +34,6 @@ const GENERATED_REFERENCE_MARKERS: [&str; 6] = [ "__udf(", "__table_fn(", ]; -const DIAGNOSTICS_MODULE: &str = "sqlbuild._native"; -const CONTRACT_MODELS_MODULE: &str = "sqlbuild._native"; -const SQL_ANALYSIS_MODELS_MODULE: &str = "sqlbuild._native"; const BUILTINS_MODULE: &str = "builtins"; const MACRO_TOKEN: &str = "@"; const GENERATED_ALLOWED: &str = "macro-generated references"; @@ -64,8 +53,7 @@ struct ExpansionRequest<'py> { } fn generated_classes(py: Python<'_>) -> PyResult> { - let native: Bound<'_, PyAny> = py.import(NATIVE_MODULE)?.into_any(); - let help: Bound<'_, PyAny> = native.getattr("explicit_references_help")?.call( + let help: Bound<'_, PyAny> = registry::native(py, NativeAttr::ExplicitReferencesHelp)?.call( (), Some(&keywords( py, @@ -78,23 +66,23 @@ fn generated_classes(py: Python<'_>) -> PyResult> { &[ ( "compiler_diagnostic", - imported(py, COMPILE_MODELS_MODULE, "CompilerDiagnostic")?, + registry::class(py, RecordId::CompilerDiagnostic)?, ), ( "report_diagnostic", - imported(py, DIAGNOSTICS_MODULE, "report_compile_diagnostic")?, + registry::native(py, NativeAttr::ReportCompileDiagnostic)?, ), ( "source_location", - imported(py, CONTRACT_MODELS_MODULE, "SourceLocation")?, + registry::class(py, RecordId::SourceLocation)?, ), ( "resource_identity", - imported(py, SCOPES_MODELS_MODULE, "ResourceIdentity")?, + registry::class(py, RecordId::ResourceIdentity)?, ), ( "generic_syntax", - imported(py, SQL_ANALYSIS_MODELS_MODULE, "SqlLexicalSyntax")? + registry::class(py, RecordId::SqlLexicalSyntax)? .call0()? .getattr("native_mapping")?, ), @@ -105,9 +93,8 @@ fn generated_classes(py: Python<'_>) -> PyResult> { } fn built_classes(py: Python<'_>) -> PyResult> { - let native: Bound<'_, PyAny> = py.import(NATIVE_MODULE)?.into_any(); - let declaration_kind: Bound<'_, PyAny> = imported(py, SCOPES_TYPES_MODULE, "DeclarationKind")?; - let ref_kind: Bound<'_, PyAny> = imported(py, REF_TYPES_MODULE, "SqlResourceRefKind")?; + let declaration_kind: Bound<'_, PyAny> = registry::class(py, RecordId::DeclarationKind)?; + let ref_kind: Bound<'_, PyAny> = host::entry(py, UserCodeHost::SqlResourceRefKind)?; let ref_kinds: Bound<'_, PyDict> = PyDict::new(py); let ref_functions: Bound<'_, PyDict> = PyDict::new(py); for (kind, function) in TYPED_REFERENCE_FUNCTIONS { @@ -117,61 +104,61 @@ fn built_classes(py: Python<'_>) -> PyResult> { let store_tokens: Bound<'_, PyDict> = keywords( py, &[ - ("macro_token", native.getattr("macro_store_token")?), - ("context_token", native.getattr("context_store_token")?), + ( + "macro_token", + registry::native(py, NativeAttr::MacroStoreToken)?, + ), + ( + "context_token", + registry::native(py, NativeAttr::ContextStoreToken)?, + ), ], )?; keywords( py, &[ - ( - "compile_error", - imported(py, COMPILE_EXCEPTIONS_MODULE, "CompileInputError")?, - ), + ("compile_error", registry::error(py, ErrorId::CompileInput)?), ( "macro_argument_error", - imported(py, COMPILE_EXCEPTIONS_MODULE, "MacroArgumentError")?, + registry::error(py, ErrorId::MacroArgument)?, ), ( "mismatch_error", - imported(py, FRONTIER_EXCEPTIONS_MODULE, "NativeStageMismatchError")?, + registry::error(py, ErrorId::NativeStageMismatch)?, ), ( "declaration_identity", - imported(py, SCOPES_MODELS_MODULE, "DeclarationIdentity")?, + registry::class(py, RecordId::DeclarationIdentity)?, ), ("macro_kind", declaration_kind.getattr("MACRO")?), ("declaration_kind", declaration_kind), - ( - "usage_record", - imported(py, SCOPES_MODELS_MODULE, "UsageRecord")?, - ), + ("usage_record", registry::class(py, RecordId::UsageRecord)?), ( "runtime_usage", - imported(py, SCOPES_TYPES_MODULE, "UsageKind")?.getattr("RUNTIME")?, + registry::class(py, RecordId::UsageKind)?.getattr("RUNTIME")?, ), ( "resource_identity", - imported(py, SCOPES_MODELS_MODULE, "ResourceIdentity")?, + registry::class(py, RecordId::ResourceIdentity)?, ), ( "model_kind", - imported(py, SCOPES_TYPES_MODULE, "ResourceKind")?.getattr("MODEL")?, + registry::class(py, RecordId::ResourceKind)?.getattr("MODEL")?, ), ( "resource_ref", - imported(py, REF_MODELS_MODULE, "SqlResourceRef")?, + host::entry(py, UserCodeHost::SqlResourceRef)?, ), ("ref_kinds", ref_kinds.into_any()), ("ref_functions", ref_functions.into_any()), ("store_tokens", store_tokens.into_any()), ( "relation_renderer", - imported(py, REF_MODELS_MODULE, "RELATION_RENDERER")?, + host::entry(py, UserCodeHost::RelationRenderer)?, ), ( "invocation_context", - imported(py, USER_MACROS_MODULE, "macro_invocation_context")?, + host::entry(py, UserCodeHost::MacroInvocationContext)?, ), ("generated", generated_classes(py)?.into_any()), ("int_type", imported(py, BUILTINS_MODULE, "int")?), @@ -200,10 +187,7 @@ fn result_model<'py>( py: Python<'py>, pairs: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - call_with( - &imported(py, NATIVE_RECORDS_MODULE, "MacroExpansionResult")?, - pairs, - ) + call_with(®istry::class(py, RecordId::MacroExpansionResult)?, pairs) } fn expansion_result<'py>(request: ExpansionRequest<'py>) -> PyResult> { @@ -238,10 +222,9 @@ fn expansion_result<'py>(request: ExpansionRequest<'py>) -> PyResult = Bound::new(py, expansion)?; - let bridge: Bound<'py, PyAny> = - imported(py, ACTIVE_BRIDGE_MODULE, "active_macro_bridge")?.call0()?; + let bridge: Bound<'py, PyAny> = host::entry(py, UserCodeHost::ActiveMacroBridge)?.call0()?; let output: ExpansionOutput = expanded(&expansion, request.sql.to_str()?, &bridge)?; - let span_class: Bound<'py, PyAny> = imported(py, NATIVE_RECORDS_MODULE, "ExpansionSpan")?; + let span_class: Bound<'py, PyAny> = registry::class(py, RecordId::ExpansionSpan)?; let mut spans: Vec> = Vec::with_capacity(output.spans.len()); for span in output.spans { let (source_start, source_end, output_start, output_end) = span_row(span); @@ -263,8 +246,7 @@ fn expansion_result<'py>(request: ExpansionRequest<'py>) -> PyResult = - imported(py, SQL_ANALYSIS_MODELS_MODULE, "SqlLexicalSyntax")?.call0()?; + let syntax: Bound<'py, PyAny> = registry::class(py, RecordId::SqlLexicalSyntax)?.call0()?; extract_references(&calls.join(" "), &syntax)?.into_any() }; result_model( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/result_output/aggregation.rs b/crates/sqlbuild-python/src/bindings/_helpers/result_output/aggregation.rs index 9c892671f1..cf0ecef542 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/result_output/aggregation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/result_output/aggregation.rs @@ -7,10 +7,10 @@ use sqlbuild_core::build_aggregate::main::aggregate_build_counts::aggregate_buil use sqlbuild_core::build_aggregate::models::{BuildCounts, BuildResultFacts, ModelResultFacts}; use crate::bindings::_helpers::boundary::keyword_request::Request; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; -const BUILD_MODELS_MODULE: &str = "sqlbuild.executor.build.models"; -const BUILD_TYPES_MODULE: &str = "sqlbuild.executor.build.types"; const RESULT_FIELDS: [&str; 7] = [ "model_results", "seed_results", @@ -68,7 +68,7 @@ pub(crate) fn aggregated_build_result<'py>( sections: &[Bound<'py, PyAny>], ) -> PyResult> { let counts: BuildCounts = aggregate_build_counts(&facts(sections)?); - let status: Bound<'py, PyAny> = imported(py, BUILD_TYPES_MODULE, "BuildStatus")? + let status: Bound<'py, PyAny> = host::entry(py, ExecutorHost::BuildStatus)? .getattr(if counts.failed { "FAILED" } else { "SUCCESS" })?; let mut keywords: Vec<(&str, Bound<'py, PyAny>)> = vec![("status", status)]; keywords.extend(RESULT_FIELDS.iter().copied().zip(sections.iter().cloned())); @@ -83,7 +83,7 @@ pub(crate) fn aggregated_build_result<'py>( ), ]); call_with( - &imported(py, BUILD_MODELS_MODULE, "BuildExecutionResult")?, + &host::entry(py, ExecutorHost::BuildExecutionResult)?, &keywords, ) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/result_output/documents.rs b/crates/sqlbuild-python/src/bindings/_helpers/result_output/documents.rs index 4b5798fdde..836e7da804 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/result_output/documents.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/result_output/documents.rs @@ -4,8 +4,10 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyRes use pyo3::types::{PyDict, PyDictMethods, PyList, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; -use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; use crate::bindings::_helpers::compile_outputs::json_report::indented_json; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::plan_output::values::{any, dict, items, text}; use crate::bindings::_helpers::result_output::aggregation::aggregated_from_lists; use crate::bindings::_helpers::result_output::assets::{ @@ -44,7 +46,6 @@ const DEGRADABLE_BUILD_OPTIONS: (&[&str], &[&str]) = ( "cost", ], ); -const OBSERVABILITY_ERRORS: &str = "sqlbuild.runtime.observability.exceptions"; const SCENARIO_OPTIONS: (&[&str], &[&str]) = ( &["results"], &["local", "run_namespace", "namespace_source", "duration_ms"], @@ -244,7 +245,7 @@ fn format_degradable_build_execution_json<'py>( || error.is_instance_of::(py) || error.is_instance( py, - &imported(py, OBSERVABILITY_ERRORS, "ObservabilityValidationError")?, + &host::entry(py, ExecutorHost::ObservabilityValidationError)?, ); if !degradable { return Err(error); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/result_output/integration_values.rs b/crates/sqlbuild-python/src/bindings/_helpers/result_output/integration_values.rs index b98ab9a191..5ad6ed5ace 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/result_output/integration_values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/result_output/integration_values.rs @@ -9,17 +9,15 @@ use sqlbuild_core::integration_results::errors::IntegrationRecordError; use sqlbuild_core::integration_results::main::encode_integration_value::encode_integration_value; use sqlbuild_core::json::models::{JsonInteger, JsonValue}; -use crate::bindings::_helpers::compile_driver::python_calls::imported; - -const OBSERVABILITY_ERRORS: &str = "sqlbuild.runtime.observability.exceptions"; -const VALIDATION_ERROR: &str = "ObservabilityValidationError"; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; /// A contract failure, or a Python failure while reading the values. pub(crate) type Checked = PyResult>; /// The `ObservabilityValidationError` Python raised for the same contract failure. pub(crate) fn validation_error(py: Python<'_>, error: &IntegrationRecordError) -> PyErr { - match imported(py, OBSERVABILITY_ERRORS, VALIDATION_ERROR) + match host::entry(py, ExecutorHost::ObservabilityValidationError) .and_then(|class| class.call1((error.message.as_str(),))) { Ok(instance) => PyErr::from_value(instance), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/result_output/projection.rs b/crates/sqlbuild-python/src/bindings/_helpers/result_output/projection.rs index 1cc2df689e..a2fdde3fb4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/result_output/projection.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/result_output/projection.rs @@ -3,19 +3,12 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyRef, PyResult, Python}; use pyo3::types::{PyFloat, PyInt}; -use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::plan_output::values::text; use crate::bindings::_helpers::result_output::resource_ids::{audit_identity, sql_test_identity}; use crate::bindings::_helpers::result_output::terminal_index::{TerminalEventIndex, current_index}; -const RUN_MODELS: &str = "sqlbuild.executor.run.models"; -const BUILD_MODELS: &str = "sqlbuild.executor.build.models"; -const LOAD_MODELS: &str = "sqlbuild.executor.load.models"; -const PYTHON_NODE_MODELS: &str = "sqlbuild.executor.python_nodes.models"; -const TESTING_MODELS: &str = "sqlbuild.executor.testing.models"; -const AUDITING_MODELS: &str = "sqlbuild.executor.auditing.models"; -const CLONE_MODELS: &str = "sqlbuild.executor.clone.models"; - /// The executor result types that result output distinguishes. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub(crate) enum ResultKind { @@ -34,35 +27,25 @@ pub(crate) enum ResultKind { /// The kind of one executor result, by its Python class. pub(crate) fn result_kind(result: &Bound<'_, PyAny>) -> PyResult { let py: Python<'_> = result.py(); - let kinds: [(&str, &str, ResultKind); 9] = [ - (RUN_MODELS, "ModelExecutionResult", ResultKind::Model), - (BUILD_MODELS, "SeedExecutionResult", ResultKind::Seed), - ( - BUILD_MODELS, - "FunctionExecutionResult", - ResultKind::Function, - ), - (LOAD_MODELS, "LoadExecutionResult", ResultKind::Load), + let kinds: [(ExecutorHost, ResultKind); 9] = [ + (ExecutorHost::ModelExecutionResult, ResultKind::Model), + (ExecutorHost::SeedExecutionResult, ResultKind::Seed), + (ExecutorHost::FunctionExecutionResult, ResultKind::Function), + (ExecutorHost::LoadExecutionResult, ResultKind::Load), ( - PYTHON_NODE_MODELS, - "PythonNodeExecutionResult", + ExecutorHost::PythonNodeExecutionResult, ResultKind::PythonNode, ), ( - PYTHON_NODE_MODELS, - "PythonCheckExecutionResult", + ExecutorHost::PythonCheckExecutionResult, ResultKind::PythonCheck, ), - ( - TESTING_MODELS, - "SqlTestExecutionResult", - ResultKind::SqlTest, - ), - (AUDITING_MODELS, "AuditExecutionResult", ResultKind::Audit), - (CLONE_MODELS, "CloneItemResult", ResultKind::CloneItem), + (ExecutorHost::SqlTestExecutionResult, ResultKind::SqlTest), + (ExecutorHost::AuditExecutionResult, ResultKind::Audit), + (ExecutorHost::CloneItemResult, ResultKind::CloneItem), ]; - for (module, class_name, kind) in kinds { - if result.is_instance(&imported(py, module, class_name)?)? { + for (class, kind) in kinds { + if result.is_instance(&host::entry(py, class)?)? { return Ok(kind); } } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/result_output/terminal_index.rs b/crates/sqlbuild-python/src/bindings/_helpers/result_output/terminal_index.rs index 5df6e3d505..df2a6ce8d4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/result_output/terminal_index.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/result_output/terminal_index.rs @@ -11,6 +11,8 @@ use pyo3::{pyclass, pyfunction, pymethods, wrap_pyfunction}; use sqlbuild_core::terminal_events::main::claim_resource_terminal::claim_resource_terminal; use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use sqlbuild_core::terminal_events::main::consume_terminal_fact::consume_terminal_fact; use sqlbuild_core::terminal_events::main::latest_resource_terminal::latest_resource_terminal; use sqlbuild_core::terminal_events::main::lifecycle_terminal::lifecycle_terminal; @@ -19,7 +21,6 @@ use sqlbuild_core::terminal_events::models::{TerminalFact, TerminalIndex, Termin /// The module attribute holding the `ContextVar` of the installed index. const CONTEXT_ATTRIBUTE: &str = "_terminal_event_index_context"; const CONTEXT_NAME: &str = "sqlbuild_terminal_event_index"; -const NATIVE_MODULE: &str = "sqlbuild._native"; const CLAIM_OPTIONS: (&[&str], &[&str]) = ( &["resource_name"], &["resource_id", "resource_attempt_id", "attempt_number"], @@ -229,7 +230,7 @@ pub(crate) struct TerminalEventIndexScope { } fn context_variable(py: Python<'_>) -> PyResult> { - py.import(NATIVE_MODULE)?.getattr(CONTEXT_ATTRIBUTE) + registry::native(py, NativeAttr::TerminalEventIndexContext) } #[pymethods] diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rule_records/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/rule_records/constants.rs index 7eb07bb0da..670af04e79 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rule_records/constants.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rule_records/constants.rs @@ -1,5 +1,6 @@ //! Layouts of the rule engine value records hosted natively. +use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::records::models::{FieldDefault, FieldSpec, RecordSpec}; const R_RULE_GUIDANCE: RecordSpec = RecordSpec { @@ -297,15 +298,12 @@ const R_RULES_CONFIG: RecordSpec = RecordSpec { FieldSpec::new("retired_source_tokens", FieldDefault::EmptyDict), FieldSpec::new( "sql_tests", - FieldDefault::Call("sqlbuild._native", "SqlTestRulesConfig"), - ), - FieldSpec::new( - "layout", - FieldDefault::Call("sqlbuild._native", "LayoutConfig"), + FieldDefault::NativeCall(RecordId::SqlTestRulesConfig), ), + FieldSpec::new("layout", FieldDefault::NativeCall(RecordId::LayoutConfig)), FieldSpec::new( "cache", - FieldDefault::Call("sqlbuild._native", "RulesCacheConfig"), + FieldDefault::NativeCall(RecordId::RulesCacheConfig), ), ], }; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/builtin_catalogue.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/builtin_catalogue.rs index 3a0cfaa261..9317352ed2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/builtin_catalogue.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/builtin_catalogue.rs @@ -1,6 +1,5 @@ //! The built-in rule catalogue returned as the Python `Rule` models. -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyBool, PyDict, PyDictMethods, PyString, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; @@ -11,9 +10,9 @@ use sqlbuild_rules::rules::main::selected_sql_codes::selected_sql_codes; use sqlbuild_rules::rules::models::{BuiltinOption, BuiltinOptionKind}; use crate::bindings::_helpers::boundary::panics::compiler_guard; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const MODELS_MODULE: &str = "sqlbuild._native"; -const TYPES_MODULE: &str = "sqlbuild._native"; const FAMILY_SUFFIX_LENGTH: usize = 3; fn keywords<'py>( @@ -51,19 +50,18 @@ fn option<'py>( } fn rule<'py>( - models: &Bound<'py, PyModule>, + py: Python<'py>, subjects: &Bound<'py, PyAny>, check: &Bound<'py, PyAny>, metadata: RuleMetadata, ) -> PyResult> { - let py: Python<'py> = models.py(); - let option_class: Bound<'py, PyAny> = models.getattr("RuleOption")?; + let option_class: Bound<'py, PyAny> = registry::class(py, RecordId::RuleOption)?; let options: Vec> = builtin_options(&metadata.code) .into_iter() .map(|declared| option(&option_class, declared)) .collect::>()?; let guidance: Bound<'py, PyAny> = match metadata.guidance { - Some(guidance) => shape_class(models.as_any(), "RuleGuidance")?.call( + Some(guidance) => registry::class(py, RecordId::RuleGuidance)?.call( (), Some(&keywords( py, @@ -112,7 +110,7 @@ fn rule<'py>( ("guidance", guidance), ], )?; - models.getattr("Rule")?.call((), Some(&arguments)) + registry::class(py, RecordId::Rule)?.call((), Some(&arguments)) } /// Every built-in rule as a `Rule` whose `check` is the given placeholder. @@ -120,11 +118,10 @@ pub(crate) fn builtin_rules<'py>( py: Python<'py>, check: Bound<'py, PyAny>, ) -> PyResult>> { - let models: Bound<'py, PyModule> = py.import(MODELS_MODULE)?; - let subjects: Bound<'py, PyAny> = py.import(TYPES_MODULE)?.getattr("RuleSubject")?; + let subjects: Bound<'py, PyAny> = registry::class(py, RecordId::RuleSubject)?; compiler_guard(|| Ok(catalogue()))? .into_iter() - .map(|metadata| rule(&models, &subjects, &check, metadata)) + .map(|metadata| rule(py, &subjects, &check, metadata)) .collect() } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/catalogue.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/catalogue.rs index d872186f32..5452d97113 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/catalogue.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/catalogue.rs @@ -14,13 +14,14 @@ use sqlbuild_rules::rule_catalogue::models::{CatalogueRule, OptionKind, OptionSp use crate::bindings::_helpers::boundary::panics::compiler_guard; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::builtin_catalogue::builtin_rules; use crate::bindings::_helpers::rules::custom_rule_evidence::verify_custom_rules as verify_rule_sources; use crate::bindings::_helpers::rules::selection::{rules_error, selected_rule_codes}; -const CUSTOM_CATALOGUE_MODULE: &str = "sqlbuild.rule_engine._helpers.engine._custom_catalogue"; -const CUSTOM_PAYLOADS_MODULE: &str = "sqlbuild.rule_engine._helpers.engine.custom_payloads"; -const PLANNER_SCOPE_MODULE: &str = "sqlbuild._native"; const DATACLASSES_MODULE: &str = "dataclasses"; const JSON_MODULE: &str = "json"; const BUILTINS_MODULE: &str = "builtins"; @@ -186,7 +187,7 @@ fn custom_rules<'py>(project_dir: &Bound<'py, PyAny>) -> PyResult( let payloads: Bound<'py, PyAny> = match custom_payloads { Some(payloads) if !payloads.is_none() => payloads, _ => call_with( - &imported(py, CUSTOM_PAYLOADS_MODULE, "custom_rule_payloads")?, + &host::entry(py, UserCodeHost::CustomRulePayloads)?, &[ ("catalogue", PyTuple::new(py, catalogue)?.into_any()), ("project_dir", project_dir.clone()), @@ -336,7 +337,7 @@ pub(crate) fn resolved_ignore_selectors<'py>( return Ok(config.clone()); } let replace: Bound<'py, PyAny> = imported(py, DATACLASSES_MODULE, "replace")?; - let scope: Bound<'py, PyAny> = imported(py, PLANNER_SCOPE_MODULE, "build_planner_scope")?; + let scope: Bound<'py, PyAny> = registry::native(py, NativeAttr::BuildPlannerScope)?; let path_by_key: Bound<'py, PyDict> = resource_paths(project)?; let mut resolved: Vec> = Vec::with_capacity(ignores.len()); for ignore in ignores { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/constants.rs index 613e955c91..dc8359a764 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/constants.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/constants.rs @@ -26,6 +26,3 @@ pub(crate) const CUSTOM_HOST_CANCELLED_MARKER: &str = "cancelled"; pub(crate) const TYPE_PROOF_RULE_CODES: [&str; 1] = ["SQBRCONTRACT105"]; /// Runtime identity custom-rule hosts and their cache entries record. pub(crate) const CUSTOM_HOST_RUNTIME_VERSION: &str = "sqlbuild-rules-custom-v2"; -/// Module a custom-rule host process runs. -pub(crate) const CUSTOM_HOST_LAUNCH_MODULE: &str = - "sqlbuild.rule_engine._helpers.host.custom_host_launch"; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_evaluation.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_evaluation.rs index 6908890048..6d32501eee 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_evaluation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_evaluation.rs @@ -19,6 +19,10 @@ use sqlbuild_rules::custom_rule_cache::types::ReadSet; use crate::bindings::_helpers::cache::native_store::current_code_identity; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::constants::{ CUSTOM_HOST_RUNTIME_VERSION, CUSTOM_RULES_CACHE_FILE, }; @@ -27,13 +31,6 @@ use crate::bindings::_helpers::rules::custom_hosts::{ }; use crate::bindings::_helpers::rules::custom_rule_cache::CustomRuleCache; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const RULES_ERRORS_MODULE: &str = "sqlbuild._native"; -const RULE_CONTEXT_MODULE: &str = "sqlbuild.rule_engine.classes.rule_context"; -const FACT_REPLAY_MODULE: &str = "sqlbuild.rule_engine._helpers.engine.fact_replay"; -const SCOPE_MODELS_MODULE: &str = "sqlbuild._native"; const PROJECT_SUBJECT: &str = "project"; const MODEL_SUBJECT: &str = "model"; /// Project fields no rule fact reads, cleared before digests and host payloads use the project. @@ -109,7 +106,7 @@ pub(crate) fn evaluate_custom_rules_cached<'py>( let cache_path: Option = if enabled { Some( call_with( - &imported(py, NATIVE_MODULE, "rules_bulk_cache_path")?, + ®istry::native(py, NativeAttr::RulesBulkCachePath)?, &[ ("project_dir", run.project_dir.clone()), ( @@ -182,7 +179,7 @@ pub(crate) fn outcome<'py>( (custom_ms, custom_cpu_ms): (i64, i64), ) -> PyResult> { call_with( - &imported(py, NATIVE_RECORDS_MODULE, "CustomRulesOutcome")?, + ®istry::class(py, RecordId::CustomRulesOutcome)?, &[ ("findings", PyTuple::new(py, findings)?.into_any()), ("cache_hits", hits.into_bound_py_any(py)?), @@ -265,7 +262,7 @@ impl<'py> FactDigests<'py> { project_dir: project_dir.clone(), dialect: dialect.to_owned(), views: None, - shared: imported(project.py(), FACT_REPLAY_MODULE, "shared_fact_encoder")?.call0()?, + shared: host::entry(project.py(), UserCodeHost::SharedFactEncoder)?.call0()?, digests: HashMap::new(), }) } @@ -276,7 +273,7 @@ impl<'py> FactDigests<'py> { } let py: Python<'py> = self.project.py(); let views: Bound<'py, PyAny> = call_with( - &imported(py, RULE_CONTEXT_MODULE, "build_rule_fact_views")?, + &host::entry(py, UserCodeHost::BuildRuleFactViews)?, &[ ("project", self.project.clone()), ("project_dir", self.project_dir.clone()), @@ -295,7 +292,7 @@ impl<'py> FactDigests<'py> { let py: Python<'py> = self.project.py(); let views: Bound<'py, PyAny> = self.views()?; let digest: String = call_with( - &imported(py, FACT_REPLAY_MODULE, "fact_outcome_digest")?, + &host::entry(py, UserCodeHost::FactOutcomeDigest)?, &[ ("views", views), ("key", PyTuple::new(py, key)?.into_any()), @@ -326,7 +323,7 @@ impl<'py> FactDigests<'py> { observed: &BTreeMap>, ) -> PyResult> { let py: Python<'py> = self.project.py(); - let is_tree_fact: Bound<'py, PyAny> = imported(py, FACT_REPLAY_MODULE, "is_tree_fact")?; + let is_tree_fact: Bound<'py, PyAny> = host::entry(py, UserCodeHost::IsTreeFact)?; for key in readset { if is_tree_fact.call1((PyTuple::new(py, key)?,))?.is_truthy()? && let Some(seen) = observed.get(key) @@ -341,7 +338,7 @@ impl<'py> FactDigests<'py> { /// Keys covering every fact a custom rule can observe through its context. fn full_fact_keys(&mut self) -> PyResult> { let py: Python<'py> = self.project.py(); - imported(py, FACT_REPLAY_MODULE, "full_fact_keys")? + host::entry(py, UserCodeHost::FullFactKeys)? .call1((self.views()?,))? .extract() } @@ -361,7 +358,7 @@ impl<'py> FactDigests<'py> { } fn fact_digest_error(py: Python<'_>, error: &PyErr) -> PyResult { - let class: Bound<'_, PyAny> = imported(py, RULES_ERRORS_MODULE, "FactDigestError")?; + let class: Bound<'_, PyAny> = registry::error(py, ErrorId::FactDigest)?; error.value(py).is_instance(&class) } @@ -380,8 +377,8 @@ fn host_project<'py>(project: &Bound<'py, PyAny>) -> PyResult> let py: Python<'py> = project.py(); let options: Bound<'py, PyDict> = PyDict::new(py); options.set_item("include_sql_expansions", false)?; - let record: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "compiled_project_record")?.call((project,), Some(&options))?; + let record: Bound<'py, PyAny> = registry::native(py, NativeAttr::CompiledProjectRecord)? + .call((project,), Some(&options))?; let changes: Bound<'py, PyDict> = PyDict::new(py); for (name, cleared) in HOST_PROJECT_CLEARED { match cleared { @@ -392,7 +389,7 @@ fn host_project<'py>(project: &Bound<'py, PyAny>) -> PyResult> } changes.set_item( "scope_index", - imported(py, SCOPE_MODELS_MODULE, "ScopeIndex")?.call0()?, + registry::class(py, RecordId::ScopeIndex)?.call0()?, )?; imported(py, "dataclasses", "replace")?.call((record,), Some(&changes)) } @@ -448,14 +445,14 @@ fn rule_identity( Some(fingerprint) => fingerprint.into_bound_py_any(py)?, None => { let closure: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "custom_rule_import_closure")?, + ®istry::native(py, NativeAttr::CustomRuleImportClosure)?, &[ ("rule", rule.clone()), ("project_dir", run.project_dir.clone()), ], )?; call_with( - &imported(py, NATIVE_MODULE, "custom_rule_implementation_fingerprint")?, + ®istry::native(py, NativeAttr::CustomRuleImplementationFingerprint)?, &[ ("rule", rule.clone()), ("project_dir", run.project_dir.clone()), @@ -466,7 +463,7 @@ fn rule_identity( }; let subject: Option = subject_value(rule)?; call_with( - &imported(py, NATIVE_MODULE, "custom_rule_identity")?, + ®istry::native(py, NativeAttr::CustomRuleIdentity)?, &[ ( "code_identity", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_host_observation.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_host_observation.rs index bd84c81f04..cf0cc98ebf 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_host_observation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_host_observation.rs @@ -9,9 +9,9 @@ use pyo3::prelude::{ use pyo3::types::PyTuple; use pyo3::{pyclass, pymethods}; -use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; /// The `sqlbuild._native` attribute tests set to a `CustomHostObserver` to observe host runs. const OBSERVER_ATTRIBUTE: &str = "custom_host_observer"; @@ -60,7 +60,7 @@ pub(crate) struct ObservedHosts { /// The installed observer's overrides and launch log; nothing when no test observes hosts. pub(crate) fn observed_hosts(py: Python<'_>) -> PyResult { - let installed: Bound<'_, PyAny> = imported(py, NATIVE_MODULE, OBSERVER_ATTRIBUTE)?; + let installed: Bound<'_, PyAny> = registry::native(py, NativeAttr::CustomHostObserver)?; if installed.is_none() { return Ok(ObservedHosts { cores: None, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_hosts.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_hosts.rs index a72344175d..033e2c85ad 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_hosts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_hosts.rs @@ -26,17 +26,16 @@ use sqlbuild_rules::custom_host_pool::types::{HostInvocation, HostPlan}; use sqlbuild_rules::engine::main::custom_host::run_custom_host_json; use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::native_host::constants::CUSTOM_HOST_LAUNCH_MODULE; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::constants::{ - CUSTOM_HOST_ABANDONED_SUFFIX, CUSTOM_HOST_CANCELLED_MARKER, CUSTOM_HOST_LAUNCH_MODULE, - CUSTOM_HOST_RUNTIME_VERSION, + CUSTOM_HOST_ABANDONED_SUFFIX, CUSTOM_HOST_CANCELLED_MARKER, CUSTOM_HOST_RUNTIME_VERSION, }; use crate::bindings::_helpers::rules::custom_host_observation::{ LaunchLog, ObservedHosts, before_publish, observed_hosts, record_launch, }; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const RULES_ERRORS_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const HOST_INPUTS: [&str; 3] = ["target", "rules-cache", "host-inputs"]; /// The model fields hosts decode; every other model field is dropped from their payload. const PAYLOAD_MODEL_FIELDS: [&str; 18] = [ @@ -398,7 +397,7 @@ pub(crate) fn host_payload_project<'py>( project: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = project.py(); - let model_class: Bound<'py, PyAny> = imported(py, COMPILE_MODELS_MODULE, "CompiledModel")?; + let model_class: Bound<'py, PyAny> = registry::class(py, RecordId::CompiledModel)?; let mut models: Vec> = Vec::new(); for model in project.getattr("models")?.try_iter()? { let model: Bound<'py, PyAny> = model?; @@ -409,7 +408,7 @@ pub(crate) fn host_payload_project<'py>( models.push(model_class.call((), Some(&fields))?); } let record: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "compiled_project_record")?.call1((project,))?; + registry::native(py, NativeAttr::CompiledProjectRecord)?.call1((project,))?; let options: Bound<'py, PyDict> = PyDict::new(py); options.set_item("models", PyTuple::new(py, models)?)?; options.set_item("native_project", py.None())?; @@ -476,8 +475,7 @@ fn text(path: &Path) -> String { } fn rules_error(py: Python<'_>, message: String) -> PyErr { - match imported(py, RULES_ERRORS_MODULE, "RulesError").and_then(|class| class.call1((message,))) - { + match registry::error(py, ErrorId::Rules).and_then(|class| class.call1((message,))) { Ok(instance) => PyErr::from_value(instance), Err(error) => error, } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_rule_cache.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_rule_cache.rs index d935f4bc93..a1afcc0327 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_rule_cache.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_rule_cache.rs @@ -23,11 +23,10 @@ use sqlbuild_rules::custom_rule_cache::types::{ReadSet, StoredRules, SubjectResu use crate::bindings::_helpers::boundary::keyword_request::Request; use crate::bindings::_helpers::compiled_project::values::plain_value; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::findings::{FindingClasses, invalid_finding}; -const RULES_ERRORS_MODULE: &str = "sqlbuild._native"; -const RULES_ERROR: &str = "RulesError"; - /// One custom rule's cache identity from its metadata, source fingerprint and settings. #[pyfunction] #[pyo3(signature = (**request))] @@ -50,7 +49,7 @@ fn custom_rule_identity(request: Option<&Bound<'_, PyDict>>) -> PyResult } fn rules_error(py: Python<'_>, message: String) -> PyResult { - let class: Bound<'_, PyAny> = py.import(RULES_ERRORS_MODULE)?.getattr(RULES_ERROR)?; + let class: Bound<'_, PyAny> = registry::error(py, ErrorId::Rules)?; Ok(pyo3::PyErr::from_value(class.call1((message,))?)) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_rule_evidence.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_rule_evidence.rs index 3c61e9b37e..75cbfb4da9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_rule_evidence.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_rule_evidence.rs @@ -17,14 +17,15 @@ use sqlbuild_pysource::rule_evidence::models::{ }; use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; const PATHLIB_MODULE: &str = "pathlib"; const INSPECT_MODULE: &str = "inspect"; const HASHLIB_MODULE: &str = "hashlib"; const AST_MODULE: &str = "ast"; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const RULES_ERROR: &str = "RulesError"; -const NON_HERMETIC_RULE_ERROR: &str = "NonHermeticRuleError"; +const RULES_ERROR: ErrorId = ErrorId::Rules; +const NON_HERMETIC_RULE_ERROR: ErrorId = ErrorId::NonHermeticRule; fn python_version(py: Python<'_>) -> (u8, u8) { let version = py.version_info(); @@ -131,8 +132,8 @@ fn custom_rule_test_evidence<'py>( } /// `error_class(message)` from the native module. -fn rules_error(py: Python<'_>, error_class: &str, message: String) -> PyErr { - match imported(py, NATIVE_MODULE, error_class).and_then(|class| class.call1((message,))) { +fn rules_error(py: Python<'_>, error_class: ErrorId, message: String) -> PyErr { + match registry::error(py, error_class).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/dsl_headers.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/dsl_headers.rs index e7af596e65..41f064c542 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/dsl_headers.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/dsl_headers.rs @@ -20,11 +20,9 @@ use sqlbuild_rules::dsl_headers::models::{ use sqlbuild_rules::dsl_headers::types::HeaderParser; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const LINT_MODELS_MODULE: &str = "sqlbuild._native"; -const LINT_TYPES_MODULE: &str = "sqlbuild._native"; -const DISCOVERY_ERRORS_MODULE: &str = "sqlbuild._native"; const PATHLIB_MODULE: &str = "pathlib"; const SENTINEL_PATH: &str = ""; const ENGINE: &str = "sqlbuild"; @@ -45,10 +43,10 @@ struct PyHeaderParser<'py> { impl<'py> PyHeaderParser<'py> { fn new(py: Python<'py>) -> PyResult { Ok(Self { - parse: imported(py, NATIVE_MODULE, "parse_header_values")?, + parse: registry::native(py, NativeAttr::ParseHeaderValues)?, path: imported(py, PATHLIB_MODULE, "Path")?.call1((SENTINEL_PATH,))?, - syntax_error: imported(py, DISCOVERY_ERRORS_MODULE, "ModelSqlParseError")?, - discovery_error: imported(py, DISCOVERY_ERRORS_MODULE, "DiscoveryError")?, + syntax_error: registry::error(py, ErrorId::ModelSqlParse)?, + discovery_error: registry::error(py, ErrorId::Discovery)?, rejected: RefCell::new(None), }) } @@ -129,8 +127,8 @@ struct LintClasses<'py> { impl<'py> LintClasses<'py> { fn new(py: Python<'py>) -> PyResult { Ok(Self { - violation: imported(py, LINT_MODELS_MODULE, "LintViolation")?, - severity: imported(py, LINT_TYPES_MODULE, "LintSeverity")?, + violation: registry::class(py, RecordId::LintViolation)?, + severity: registry::class(py, RecordId::LintSeverity)?, }) } @@ -211,7 +209,7 @@ fn scan_headers<'py>( first_only: bool, ) -> PyResult> { let py: Python<'py> = contents.py(); - let class: Bound<'py, PyAny> = imported(py, LINT_MODELS_MODULE, "HeaderSpan")?; + let class: Bound<'py, PyAny> = registry::class(py, RecordId::HeaderSpan)?; let headers: Vec> = scan(&text(contents)?, first_only) .into_iter() .map(|header| { @@ -320,7 +318,7 @@ fn prepare_native_header_cache(files: &Bound<'_, PyDict>) -> PyResult<()> { ); } } - imported(files.py(), NATIVE_MODULE, "prepare_model_header_tokens")?.call1((headers,))?; + registry::native(files.py(), NativeAttr::PrepareModelHeaderTokens)?.call1((headers,))?; Ok(()) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/early_sql_lint.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/early_sql_lint.rs index f372189d29..a44077bd9b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/early_sql_lint.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/early_sql_lint.rs @@ -11,12 +11,9 @@ use pyo3::{IntoPyObject, IntoPyObjectExt, pyclass, pyfunction, pymethods, wrap_p use sqlbuild_rules::rule_cache::main::rules_bulk_cache_path::rules_bulk_cache_path; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const RULE_THREADS_MODULE: &str = "sqlbuild._native"; -const LINT_MODELS_MODULE: &str = "sqlbuild._native"; /// The lint thresholds used when the project leaves them unset. const DEFAULT_MAX_LITERAL_LENGTH: i64 = 100; const DEFAULT_MAX_RANKING_ORDER_BY: i64 = 5; @@ -47,7 +44,7 @@ pub(crate) fn sql_lint_config<'py>( thresholds.call_method1("get", (key, default)) }; call_with( - &imported(py, LINT_MODELS_MODULE, "LintConfig")?, + ®istry::class(py, RecordId::LintConfig)?, &[ ("dialect", dialect.clone()), ("enabled_native_rules", PyTuple::new(py, codes)?.into_any()), @@ -74,7 +71,7 @@ fn sql_lint_preparation<'py>( dialect: &Bound<'py, PyAny>, ) -> PyResult>> { let py: Python<'py> = inputs.py(); - let native = |name: &str| imported(py, NATIVE_MODULE, name); + let native = |name: NativeAttr| registry::native(py, name); let project_dir: Bound<'py, PyAny> = inputs .getattr("discovered_inputs")? .getattr("project_dir")?; @@ -87,7 +84,7 @@ fn sql_lint_preparation<'py>( } } let config: Bound<'py, PyAny> = call_with( - &native("valid_rules_config")?, + &native(NativeAttr::ValidRulesConfig)?, &[("project_dir", project_dir.clone())], )?; if config.is_none() || !config.getattr("select")?.is_truthy()? { @@ -95,7 +92,7 @@ fn sql_lint_preparation<'py>( } if config.getattr("cache")?.getattr("enabled")?.is_truthy()? && call_with( - &native("load_sql_rule_cache")?, + &native(NativeAttr::LoadSqlRuleCache)?, &[("path", sql_rule_cache_path(&project_dir)?)], )? .is_truthy()? @@ -103,7 +100,7 @@ fn sql_lint_preparation<'py>( return Ok(None); } let codes: Vec = call_with( - &native("selected_sql_rule_codes")?, + &native(NativeAttr::SelectedSqlRuleCodes)?, &[ ( "select", @@ -146,7 +143,7 @@ fn sql_lint_preparation<'py>( sources.push(source?.getattr("source_entry")?); } let relation_keys: Bound<'py, PyAny> = call_with( - &native("declared_relation_keys")?, + &native(NativeAttr::DeclaredRelationKeys)?, &[ ("model_rows", PyList::new(py, model_rows)?.into_any()), ("seeds", PyList::new(py, seeds)?.into_any()), @@ -255,7 +252,7 @@ impl EarlySqlLint { } let py: Python<'_> = inputs.py(); let preparation: Bound<'_, PyAny> = call_with( - &imported(py, RULE_THREADS_MODULE, "prepare_sql_rules")?, + ®istry::native(py, NativeAttr::PrepareSqlRules)?, &[ ("inputs", inputs.clone()), ("executor", slf.executor.bind(py).clone()), @@ -268,7 +265,7 @@ impl EarlySqlLint { declaration_scope } else { call_with( - &imported(py, NATIVE_RECORDS_MODULE, "SqlExpansionReuse")?, + ®istry::class(py, RecordId::SqlExpansionReuse)?, &[ ("discovered_inputs", inputs.getattr("discovered_inputs")?), ("declaration_scope", declaration_scope), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/findings.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/findings.rs index 2fd567c7ae..3f1681e55a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/findings.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/findings.rs @@ -1,16 +1,14 @@ //! Rule findings converted between the Python `Finding` model and native rows and payloads. +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods}; use serde_json::Value; use sqlbuild_rules::models::Fault; -const MODELS_MODULE: &str = "sqlbuild._native"; -const FINDING_CLASS: &str = "Finding"; const PATHLIB_MODULE: &str = "pathlib"; const PATH_CLASS: &str = "Path"; -const ERRORS_MODULE: &str = "sqlbuild._native"; -const RULES_ERROR: &str = "RulesError"; const INVALID_FINDING: &str = "native rules engine returned an invalid finding"; /// The Python `Finding` and `Path` classes, imported once per conversion batch. @@ -22,7 +20,7 @@ pub(crate) struct FindingClasses<'py> { impl<'py> FindingClasses<'py> { pub(crate) fn new(py: Python<'py>) -> PyResult { Ok(Self { - finding: py.import(MODELS_MODULE)?.getattr(FINDING_CLASS)?, + finding: registry::class(py, RecordId::Finding)?, path: py.import(PATHLIB_MODULE)?.getattr(PATH_CLASS)?, }) } @@ -98,7 +96,7 @@ pub(crate) fn finding_fault(finding: &Bound<'_, PyAny>) -> PyResult { /// The `RulesError` a malformed native or custom-rule finding raises. pub(crate) fn invalid_finding(py: Python<'_>) -> PyErr { let raised = || -> PyResult { - let class: Bound<'_, PyAny> = py.import(ERRORS_MODULE)?.getattr(RULES_ERROR)?; + let class: Bound<'_, PyAny> = registry::error(py, ErrorId::Rules)?; Ok(PyErr::from_value(class.call1((INVALID_FINDING,))?)) }; raised().unwrap_or_else(|error| error) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/fix_facts.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/fix_facts.rs index 43c7b97d6e..519b5f4f15 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/fix_facts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/fix_facts.rs @@ -4,9 +4,9 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::pyfunction; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; - -const LINT_MODELS: &str = "sqlbuild._native"; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; /// Index a compilation by model key and diagnostic owner without changing its fact views. #[pyfunction] @@ -54,7 +54,7 @@ pub(crate) fn compile_facts<'py>( counts.set_item(identity, count + 1)?; } call_with( - &imported(py, LINT_MODELS, "CompileFacts")?, + ®istry::class(py, RecordId::CompileFacts)?, &[ ("models", models.into_any()), ("model_paths", model_paths.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/fix_session.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/fix_session.rs index 261eb0bf41..0c5cd5437c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/fix_session.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/fix_session.rs @@ -11,18 +11,15 @@ use sqlbuild_rules::fix_verification::main::refusal::refusal; use sqlbuild_rules::fix_verification::main::with_edits::with_edits; use sqlbuild_rules::fix_verification::models::{FixEdit, FixEligibility}; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::constants::SAFE_RULE_FIX_CODES; use crate::bindings::_helpers::rules::fix_facts::compile_facts; use crate::bindings::_helpers::rules::fix_verdict::fix_verdict; -const NATIVE: &str = "sqlbuild._native"; -const MODELS: &str = "sqlbuild._native"; -const ADAPTER: &str = "sqlbuild.adapter.contract.classes.base_adapter"; -const COMPILE_ERRORS: &str = "sqlbuild._native"; -const LINT_ERRORS: &str = "sqlbuild._native"; const VERIFICATION_FAILURE: &str = "Compilation could not prove unchanged output columns, types, dependencies and lineage"; const APPLIED_REASON: &str = "Meaning-preserving rewrite; compiler invariants verified"; @@ -77,9 +74,9 @@ pub(crate) fn plan_rule_fixes<'py>( } let adapter = args.value("adapter")?; let inputs = args.value("discovered_inputs")?; - if !adapter.is_instance(&imported(py, ADAPTER, "BaseAdapter")?)? || inputs.is_none() { + if !adapter.is_instance(&host::entry(py, AdapterHost::BaseAdapter)?)? || inputs.is_none() { return Err(PyErr::from_value( - imported(py, LINT_ERRORS, "ProjectCompileError")? + registry::error(py, ErrorId::ProjectCompile)? .call1(("format --fix requires discovered compiler inputs and an adapter",))?, )); } @@ -156,7 +153,7 @@ impl<'py> FixSession<'py> { ], )?; let lint = call_with( - &imported(py, NATIVE, "run_lint")?, + ®istry::native(py, NativeAttr::RunLint)?, &[ ("project_dir", self.project_dir.clone()), ("config", config), @@ -346,7 +343,7 @@ impl<'py> FixSession<'py> { return Ok(baseline.clone()); } let baseline = call_with( - &imported(self.inputs.py(), NATIVE, "build_compiled_project")?, + ®istry::native(self.inputs.py(), NativeAttr::BuildCompiledProject)?, &[ ("discovered_inputs", self.inputs.clone()), ("adapter", self.adapter.clone()), @@ -361,17 +358,15 @@ impl<'py> FixSession<'py> { let baseline = match self.baseline() { Ok(value) => value, Err(error) - if error.value(py).is_instance(&imported( - py, - COMPILE_ERRORS, - "CompileInputError", - )?)? => + if error + .value(py) + .is_instance(®istry::error(py, ErrorId::CompileInput)?)? => { return Ok(PyDict::new(py).into_any()); } Err(error) => return Err(error), }; - imported(py, NATIVE, "compiled_relation_keys")?.call1((baseline,)) + registry::native(py, NativeAttr::CompiledRelationKeys)?.call1((baseline,)) } fn verdict(&mut self, files: &Bound<'py, PyDict>) -> PyResult>> { @@ -390,7 +385,7 @@ impl<'py> FixSession<'py> { self.baseline_facts = Some(compile_facts(py, &baseline, &self.project_dir)?); } let compiled = call_with( - &imported(py, NATIVE, "build_compiled_project")?, + ®istry::native(py, NativeAttr::BuildCompiledProject)?, &[ ("discovered_inputs", updated_inputs(&self.inputs, files)?), ("adapter", self.adapter.clone()), @@ -584,7 +579,7 @@ fn report<'py>( ) -> PyResult> { let py = source.py(); call_with( - &imported(py, MODELS, "RuleFixResult")?, + ®istry::class(py, RecordId::RuleFixResult)?, &[ ("file_path", source.getattr("file_path")?), ("code", source.getattr("code")?), @@ -614,7 +609,7 @@ fn violation<'py>( ) -> PyResult> { let py = path.py(); call_with( - &imported(py, MODELS, "LintViolation")?, + ®istry::class(py, RecordId::LintViolation)?, &[ ("file_path", path.clone()), ("line", 1_i64.into_pyobject(py)?.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/fix_verdict.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/fix_verdict.rs index 4c88ccdba1..3f66d44e87 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/fix_verdict.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/fix_verdict.rs @@ -8,9 +8,9 @@ use pyo3::types::{PyDict, PyDictMethods, PyList, PyListMethods, PyTuple}; use sqlbuild_rules::fix_verification::main::compare::compare; use sqlbuild_rules::fix_verification::models::{DiagnosticComparison, ModelComparison}; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; - -const LINT_MODELS: &str = "sqlbuild._native"; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; /// Compare compile facts using native decisions and the existing Python boundary models. #[pyfunction] @@ -70,7 +70,7 @@ pub(crate) fn fix_verdict<'py>( failing.set_item(owners.get_item(owner)?, reason)?; } call_with( - &imported(py, LINT_MODELS, "FixVerdict")?, + ®istry::class(py, RecordId::FixVerdict)?, &[ ("failing", failing.into_any()), ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_expansion.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_expansion.rs index 9fde98d0f3..6ab29ec336 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_expansion.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_expansion.rs @@ -8,15 +8,13 @@ use pyo3::{pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::compile_attachment::sql_expansion_context::build_sql_expansion_context; use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, keywords, + KeywordArguments, call_with, keywords, }; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const RESOLVE_ADAPTER_MODULE: &str = "sqlbuild.adapter.discovery.main.resolve_adapter"; -const ADAPTER_EXCEPTIONS_MODULE: &str = "sqlbuild.adapter.contract.exceptions"; -const COMPILE_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; -const DISCOVERY_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; -const LINT_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; const COMPILE_FIRST_MESSAGE: &str = "compiler-integrated Rules check the SQL your project \ actually produces, so the project must compile first"; @@ -53,7 +51,7 @@ fn expansion_context<'py>( if discovered.is_none() { let no: Bound<'py, PyAny> = PyBool::new(py, false).to_owned().into_any(); discovered = call_with( - &imported(py, NATIVE_MODULE, "discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[ ("project_dir", project_dir.clone()), ("sql_analysis_enabled_override", no.clone()), @@ -64,14 +62,14 @@ fn expansion_context<'py>( let mut renderer: Bound<'py, PyAny> = arguments.value("value_renderer")?; if !renderer.is_truthy()? { let adapter_name: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), ], )?; renderer = call_with( - &imported(py, RESOLVE_ADAPTER_MODULE, "resolve_adapter")?, + &host::entry(py, AdapterHost::ResolveAdapter)?, &[ ("adapter_name", adapter_name), ("project_dir", project_dir.clone()), @@ -99,26 +97,15 @@ fn expansion_context<'py>( /// Adapter, compile and discovery errors as `ProjectCompileError`; any other error unchanged. fn compile_first_error(py: Python<'_>, error: PyErr) -> PyResult { let value: Bound<'_, PyAny> = error.value(py).clone().into_any(); - let reported: bool = value.is_instance(&imported( - py, - ADAPTER_EXCEPTIONS_MODULE, - "AdapterUserError", - )?)? || value.is_instance(&imported( - py, - COMPILE_EXCEPTIONS_MODULE, - "CompileInputError", - )?)? || value.is_instance(&imported( - py, - DISCOVERY_EXCEPTIONS_MODULE, - "DiscoveryError", - )?)?; + let reported: bool = value.is_instance(&host::entry(py, AdapterHost::AdapterUserError)?)? + || value.is_instance(®istry::error(py, ErrorId::CompileInput)?)? + || value.is_instance(®istry::error(py, ErrorId::Discovery)?)?; if !reported { return Ok(error); } let message: String = format!("{COMPILE_FIRST_MESSAGE}: {}", value.str()?); - let raised: PyErr = PyErr::from_value( - imported(py, LINT_EXCEPTIONS_MODULE, "ProjectCompileError")?.call1((message,))?, - ); + let raised: PyErr = + PyErr::from_value(registry::error(py, ErrorId::ProjectCompile)?.call1((message,))?); raised.set_cause(py, Some(error)); Ok(raised) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_headers.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_headers.rs index 06c6b34fd9..0d34892772 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_headers.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_headers.rs @@ -4,12 +4,9 @@ use pyo3::prelude::{Bound, IntoPyObject, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::pyfunction; use pyo3::types::PyDict; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; - -const NATIVE: &str = "sqlbuild._native"; -const MODELS: &str = "sqlbuild._native"; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; /// Classify a lint path, retaining the caller's supplied relative path when available. #[pyfunction] @@ -27,17 +24,17 @@ pub(crate) fn lint_file_role<'py>( .call_method1("is_relative_to", (project_dir,))? .is_truthy()? { - return imported(py, MODELS, "LintFileRole")?.call0(); + return registry::class(py, RecordId::LintFileRole)?.call0(); } file_path.call_method1("relative_to", (project_dir,))? }; call_with( - &imported(py, MODELS, "LintFileRole")?, + ®istry::class(py, RecordId::LintFileRole)?, &[ ( "declaration_kind", call_with( - &imported(py, NATIVE, "named_declaration_role_kind")?, + ®istry::native(py, NativeAttr::NamedDeclarationRoleKind)?, &[("relative_path", relative.clone())], )?, ), @@ -75,7 +72,7 @@ pub(crate) fn lint_body_ranges<'py>( && (kind.eq("audit")? || kind.eq("singular_audit")?) { let ranges = call_with( - &imported(py, NATIVE, "measurement_body_ranges")?, + ®istry::native(py, NativeAttr::MeasurementBodyRanges)?, &[("contents", args.value("contents")?)], )?; if ranges.is_truthy()? { @@ -83,7 +80,7 @@ pub(crate) fn lint_body_ranges<'py>( } } call_with( - &imported(py, NATIVE, "sql_body_ranges")?, + ®istry::native(py, NativeAttr::SqlBodyRanges)?, &[ ("contents", args.value("contents")?), ("headers", args.value("headers")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_preparation.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_preparation.rs index 4adab35a94..d571ba353b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_preparation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_preparation.rs @@ -7,18 +7,13 @@ use sqlbuild_rules::lint_text::main::dependency_sites::dependency_sites; use sqlbuild_rules::lint_text::models::InterpolationSite; use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, keywords, + KeywordArguments, call_with, keywords, }; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::lint_headers::{lint_body_ranges, lint_file_role}; use crate::bindings::_helpers::rules::lint_stop::check_stop; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE: &str = "sqlbuild._native"; -const MODELS: &str = "sqlbuild._native"; -const COMPILE_ERRORS: &str = "sqlbuild._native"; -const LINT_ERRORS: &str = "sqlbuild._native"; - /// Expand a single body and finish its native SQL preparation. #[pyfunction] #[pyo3(signature = (**options))] @@ -28,7 +23,7 @@ pub(crate) fn prepare_lint_body<'py>( ) -> PyResult> { let pending = expand_lint_body(py, options)?; let prepared = - imported(py, NATIVE, "prepare_lint_sql")?.call1((pending.getattr("request")?,))?; + registry::native(py, NativeAttr::PrepareLintSql)?.call1((pending.getattr("request")?,))?; finish_body(&pending, &prepared) } @@ -73,16 +68,16 @@ pub(crate) fn expand_lint_body<'py>( let kind = role.getattr("declaration_kind")?; let in_project = role.getattr("in_project")?.is_truthy()?; let neutralizer = if in_project && kind.eq("audit")? { - Some("neutralize_lint_audit_parameters") + Some(NativeAttr::NeutralizeLintAuditParameters) } else if role.getattr("in_hook_directory")?.is_truthy()? || (in_project && kind.eq("sql_hook")?) { - Some("neutralize_lint_context_interpolation") + Some(NativeAttr::NeutralizeLintContextInterpolation) } else { None }; if let Some(neutralizer) = neutralizer { - let neutralized = imported(py, NATIVE, neutralizer)?.call1((&body,))?; + let neutralized = registry::native(py, neutralizer)?.call1((&body,))?; input = neutralized.get_item(0)?; sites = site_objects(&neutralized.get_item(1)?)?; } @@ -106,7 +101,7 @@ pub(crate) fn expand_lint_body<'py>( passes = PyTuple::empty(py).into_any(); } else { let value = call_with( - &imported(py, NATIVE, "expand_sql_with_spans")?, + ®istry::native(py, NativeAttr::ExpandSqlWithSpans)?, &[ ("sql", input.clone()), ("file_path", args.value("file_path")?), @@ -136,7 +131,7 @@ pub(crate) fn expand_lint_body<'py>( PyTuple::empty(py).into_any() }; call_with( - &imported(py, MODELS, "PendingLintBody")?, + ®istry::class(py, RecordId::PendingLintBody)?, &[ ("file_path", args.value("file_path")?), ("body_start", start.into_pyobject(py)?.into_any()), @@ -167,7 +162,7 @@ fn expansion_error(py: Python<'_>, args: &KeywordArguments<'_>, error: PyErr) -> let wrapped = (|| -> PyResult> { if !error .value(py) - .is_instance(&imported(py, COMPILE_ERRORS, "CompileInputError")?)? + .is_instance(®istry::error(py, ErrorId::CompileInput)?)? { return Ok(None); } @@ -177,7 +172,7 @@ fn expansion_error(py: Python<'_>, args: &KeywordArguments<'_>, error: PyErr) -> error.value(py).str()? ); Ok(Some(PyErr::from_value( - imported(py, LINT_ERRORS, "ProjectCompileError")?.call1((message,))?, + registry::error(py, ErrorId::ProjectCompile)?.call1((message,))?, ))) })(); match wrapped { @@ -191,7 +186,7 @@ fn expansion_error(py: Python<'_>, args: &KeywordArguments<'_>, error: PyErr) -> } fn site_objects<'py>(sites: &Bound<'py, PyAny>) -> PyResult> { - let class = imported(sites.py(), MODELS, "InterpolationSite")?; + let class = registry::class(sites.py(), RecordId::InterpolationSite)?; let values: Vec> = sites .try_iter()? .map(|site| class.call1(site?.cast_into::()?)) @@ -205,7 +200,7 @@ fn sentinel_spans<'py>(sites: &Bound<'py, PyAny>) -> PyResult> for site in sites.try_iter()? { let site = site?; spans.push(call_with( - &imported(py, NATIVE_RECORDS_MODULE, "ExpansionSpan")?, + ®istry::class(py, RecordId::ExpansionSpan)?, &[ ("source_start", site.getattr("original_start")?), ("source_end", site.getattr("original_end")?), @@ -224,7 +219,7 @@ fn finish_body<'py>( let py = pending.py(); if prepared.is_none() { return Err(PyErr::from_value( - imported(py, LINT_ERRORS, "NativeLintError")?.call1((format!( + registry::error(py, ErrorId::NativeLint)?.call1((format!( "{} could not be prepared for native SQL lint", pending.getattr("file_path")?.str()? ),))?, @@ -286,7 +281,7 @@ fn finish_body<'py>( ] { values.set_item(name, pending.getattr(name)?)?; } - imported(py, MODELS, "LintBody")?.call((), Some(&values)) + registry::class(py, RecordId::LintBody)?.call((), Some(&values)) } fn prepare_batch<'py>( @@ -300,15 +295,15 @@ fn prepare_batch<'py>( .iter() .map(|body| body.getattr("request")) .collect::>()?; - let results = - imported(py, NATIVE, "prepare_lint_sql_batch")?.call1((PyList::new(py, requests)?,))?; + let results = registry::native(py, NativeAttr::PrepareLintSqlBatch)? + .call1((PyList::new(py, requests)?,))?; let mut prepared: Vec> = Vec::new(); for (body, result) in pending.iter().zip(results.try_iter()?) { let result = result?; prepared.push(if result.get_item(0)?.is_truthy()? { result.get_item(1)? } else { - imported(py, NATIVE, "prepare_lint_sql")?.call1((body.getattr("request")?,))? + registry::native(py, NativeAttr::PrepareLintSql)?.call1((body.getattr("request")?,))? }); } if prepared.len() != pending.len() || results.len()? != pending.len() { @@ -361,7 +356,7 @@ pub(crate) fn expand_file_bodies<'py>( py.None().into_bound(py) }; let external = call_with( - &imported(py, NATIVE, "external_identifiers_for_headers")?, + ®istry::native(py, NativeAttr::ExternalIdentifiersForHeaders)?, &[ ("contents", args.value("contents")?), ("headers", args.value("headers")?), @@ -434,14 +429,14 @@ pub(crate) fn expand_file_bodies<'py>( if let Err(error) = expansion { if !error .value(py) - .is_instance(&imported(py, LINT_ERRORS, "ProjectCompileError")?)? + .is_instance(®istry::error(py, ErrorId::ProjectCompile)?)? { return Err(error); } failure = error.value(py).clone().into_any(); } call_with( - &imported(py, MODELS, "ExpandedLintFile")?, + ®istry::class(py, RecordId::ExpandedLintFile)?, &[ ("file_path", path), ("bodies", PyTuple::new(py, bodies)?.into_any()), @@ -493,7 +488,7 @@ pub(crate) fn prepare_expanded_files<'py>( } } call_with( - &imported(py, MODELS, "PreparedLintFiles")?, + ®istry::class(py, RecordId::PreparedLintFiles)?, &[ ("bodies", PyTuple::new(py, bodies)?.into_any()), ("unexpandable", unexpandable.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_sql_bodies.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_sql_bodies.rs index cd59b9bc4a..3020f23ec0 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_sql_bodies.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_sql_bodies.rs @@ -9,11 +9,11 @@ use sqlbuild_rules::lint_text::models::{ }; use crate::bindings::_helpers::boundary::panics::value_error; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::types::CompilerDetach; -const LINT_MODELS_MODULE: &str = "sqlbuild._native"; -const LINT_TYPES_MODULE: &str = "sqlbuild._native"; const NATIVE_ENGINE: &str = "native"; fn spans(passes: &Bound<'_, PyAny>) -> PyResult>> { @@ -94,9 +94,9 @@ struct ViolationClasses<'py> { impl<'py> ViolationClasses<'py> { fn new(py: Python<'py>) -> PyResult { Ok(Self { - violation: imported(py, LINT_MODELS_MODULE, "LintViolation")?, - edit: imported(py, LINT_MODELS_MODULE, "LintEdit")?, - severity: imported(py, LINT_TYPES_MODULE, "LintSeverity")?, + violation: registry::class(py, RecordId::LintViolation)?, + edit: registry::class(py, RecordId::LintEdit)?, + severity: registry::class(py, RecordId::LintSeverity)?, }) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_stop.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_stop.rs index 6c0a379e09..3f0801a4dd 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_stop.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_stop.rs @@ -9,9 +9,10 @@ use pyo3::sync::PyOnceLock; use pyo3::types::PyTuple; use pyo3::{pyclass, pyfunction, pymethods, wrap_pyfunction}; -use crate::bindings::_helpers::compile_driver::python_calls::{context_variable, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::context_variable; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; -const LINT_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; const STOP_VARIABLE: &str = "active_lint_stop"; static ACTIVE_STOP: PyOnceLock> = PyOnceLock::new(); @@ -26,7 +27,7 @@ pub(crate) fn check_stop(py: Python<'_>) -> PyResult<()> { let stop: Bound<'_, PyAny> = active_stop_variable(py)?.call_method0("get")?; if !stop.is_none() && stop.call_method0("is_set")?.is_truthy()? { return Err(PyErr::from_value( - imported(py, LINT_EXCEPTIONS_MODULE, "LintStoppedError")?.call0()?, + registry::error(py, ErrorId::LintStopped)?.call0()?, )); } Ok(()) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_text.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_text.rs index ccc75757da..6ae4847acc 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_text.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_text.rs @@ -23,12 +23,11 @@ use sqlbuild_rules::lint_text::models::{ }; use crate::bindings::_helpers::boundary::panics::{compiler_guard, value_error}; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::types::CompilerDetach; -const LINT_MODELS_MODULE: &str = "sqlbuild._native"; -const LINT_TYPES_MODULE: &str = "sqlbuild._native"; -const SCOPE_TYPES_MODULE: &str = "sqlbuild._native"; const NATIVE_ENGINE: &str = "native"; const FORMAT_UNSAFE_CODE: &str = "format-unsafe"; const FORMAT_UNSAFE_REMEDIATION: &str = "Rewrite the unsupported SQL shape or report a minimal formatter reproduction. No header, SQL body, or fixture changes were written to this file."; @@ -130,7 +129,7 @@ fn named_declaration_role_kind<'py>( let parts: Vec = relative_path.getattr("parts")?.extract()?; match role_kind(&parts) { Some(kind) => Ok(Some( - imported(relative_path.py(), SCOPE_TYPES_MODULE, "DeclarationKind")?.call1((kind,))?, + registry::class(relative_path.py(), RecordId::DeclarationKind)?.call1((kind,))?, )), None => Ok(None), } @@ -197,9 +196,9 @@ struct FindingClasses<'py> { impl<'py> FindingClasses<'py> { fn new(py: Python<'py>) -> PyResult { Ok(Self { - violation: imported(py, LINT_MODELS_MODULE, "LintViolation")?, - edit: imported(py, LINT_MODELS_MODULE, "LintEdit")?, - severity: imported(py, LINT_TYPES_MODULE, "LintSeverity")?, + violation: registry::class(py, RecordId::LintViolation)?, + edit: registry::class(py, RecordId::LintEdit)?, + severity: registry::class(py, RecordId::LintSeverity)?, }) } @@ -292,9 +291,8 @@ fn format_lint_sql_bodies<'py>( for (index, text) in result.formatted { formatted.set_item(&files[index].0, text)?; } - let violation: Bound<'py, PyAny> = imported(py, LINT_MODELS_MODULE, "LintViolation")?; - let fault: Bound<'py, PyAny> = - imported(py, LINT_TYPES_MODULE, "LintSeverity")?.getattr("FAULT")?; + let violation: Bound<'py, PyAny> = registry::class(py, RecordId::LintViolation)?; + let fault: Bound<'py, PyAny> = registry::class(py, RecordId::LintSeverity)?.getattr("FAULT")?; let faults: Vec> = result .faults .into_iter() diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/project_tree_queries.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/project_tree_queries.rs index 62a6f2b4dd..4fceef9822 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/project_tree_queries.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/project_tree_queries.rs @@ -17,11 +17,11 @@ use sqlbuild_rules::project_tree::main::project_tree_glob::project_tree_glob; use sqlbuild_rules::project_tree::main::tracked_text_path::tracked_text_path; use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::rule_api::{usage_error_for, validate_project_relative}; const PATHLIB_MODULE: &str = "pathlib"; -const RULE_USAGE_ERROR: &str = "RuleUsageError"; -const NATIVE_MODULE: &str = "sqlbuild._native"; /// `pathlib.Path(value)`, raising exactly as `Path` does for an unsupported value. fn python_path<'py>(value: &Bound<'py, PyAny>) -> PyResult> { @@ -126,7 +126,7 @@ fn not_beneath(path: &Bound<'_, PyAny>, under: &Bound<'_, PyAny>) -> PyErr { .add(under_text) }); match message.and_then(|message| { - imported(py, NATIVE_MODULE, RULE_USAGE_ERROR).and_then(|class| class.call1((message,))) + registry::error(py, ErrorId::RuleUsage).and_then(|class| class.call1((message,))) }) { Ok(error) => PyErr::from_value(error), Err(error) => error, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/rows.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/rows.rs index 0c8e28ffef..b922e1e5ab 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/rows.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/rows.rs @@ -32,15 +32,14 @@ use crate::bindings::_helpers::compile_driver::python_calls::{call_with, importe use crate::bindings::_helpers::compiled_project::project::NativeCompiledProject; use crate::bindings::_helpers::compiled_project::records::declaration_rows; use crate::bindings::_helpers::compiled_project::values::plain_value; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::findings::{FindingClasses, finding_fault}; use crate::bindings::_helpers::rules::selection::rules_error; use crate::bindings::_helpers::rules::sql_test_objects::{scenario_fact, sql_test_fact}; use crate::bindings::_helpers::type_system::equality::types_equal; use crate::bindings::types::CompilerDetach; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; const DATACLASSES_MODULE: &str = "dataclasses"; const NATIVE_RULES_CACHE_FILE: &str = "native.json"; const NATIVE_RULES_MEMO_FILE: &str = "native-response.json"; @@ -116,7 +115,7 @@ fn built_request(options: &Request<'_, '_>) -> PyResult { "dialect": dialect.extract::()?, "config": config_value(&config)?, "scope_index": plain_value(&call_with( - &imported(py, NATIVE_MODULE, "scope_metadata_projection")?, + ®istry::native(py, NativeAttr::ScopeMetadataProjection)?, &[("index", project.getattr("scope_index")?)], )?)?, "defer_suppressions": true, @@ -164,7 +163,7 @@ fn built_request(options: &Request<'_, '_>) -> PyResult { dialect, }; let facts: Bound<'_, NativeCompiledProject> = - imported(py, NATIVE_MODULE, "compiled_project_facts")? + registry::native(py, NativeAttr::CompiledProjectFacts)? .call1((&project,))? .downcast_into()?; let mut names: Vec = Vec::new(); @@ -243,7 +242,7 @@ fn evaluated<'py>( .map(|fault| classes.finding_of_fault(fault, false)) .collect::>()?; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "NativeRulesEvaluation")?, + ®istry::class(py, RecordId::NativeRulesEvaluation)?, &[ ("findings", PyTuple::new(py, findings)?.into_any()), ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_api.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_api.rs index 415d36751c..04c5f89a14 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_api.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_api.rs @@ -20,10 +20,9 @@ use sqlbuild_rules::project_tree::main::tracked_text_suffixes::tracked_text_suff use sqlbuild_rules::project_tree::models::ProjectTreeEntry; use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const RULE_USAGE_ERROR: &str = "RuleUsageError"; -const PROJECT_PATH_CLASS: &str = "ProjectPath"; const BUILTINS_MODULE: &str = "builtins"; const DEFAULT_POSITION: i64 = 1; #[cfg(unix)] @@ -33,7 +32,7 @@ const EMBEDDED_NUL_MESSAGE: &str = "lstat: embedded null character in path"; /// `RuleUsageError(message)`. fn usage_error(py: Python<'_>, message: String) -> PyErr { - match imported(py, NATIVE_MODULE, RULE_USAGE_ERROR).and_then(|class| class.call1((message,))) { + match registry::error(py, ErrorId::RuleUsage).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } @@ -46,7 +45,7 @@ pub(crate) fn usage_error_for(prefix: &str, value: &Bound<'_, PyAny>) -> PyErr { .str() .and_then(|text| PyString::new(py, prefix).as_any().add(text)); match message.and_then(|message| { - imported(py, NATIVE_MODULE, RULE_USAGE_ERROR).and_then(|class| class.call1((message,))) + registry::error(py, ErrorId::RuleUsage).and_then(|class| class.call1((message,))) }) { Ok(error) => PyErr::from_value(error), Err(error) => error, @@ -187,7 +186,7 @@ fn unreadable(py: Python<'_>, path: PathBuf, code: i32) -> PyResult { fn project_tree_paths<'py>(root: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = root.py(); let root: PathBuf = root.extract()?; - let class: Bound<'py, PyAny> = imported(py, NATIVE_MODULE, PROJECT_PATH_CLASS)?; + let class: Bound<'py, PyAny> = registry::class(py, RecordId::ProjectPath)?; let mut paths: Vec<(Bound<'py, PyAny>, bool)> = project_tree_entries(&root) .into_iter() .map(|ProjectTreeEntry { relative, is_file }| { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_dispatch.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_dispatch.rs index 9ef1718718..5146f908a7 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_dispatch.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_dispatch.rs @@ -16,8 +16,12 @@ use pyo3::{IntoPyObjectExt, pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::cache::native_store::current_code_identity; use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, within, + KeywordArguments, call_with, within, }; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::constants::TYPE_PROOF_RULE_CODES; use crate::bindings::_helpers::rules::custom_evaluation::{ CustomRun, evaluate_custom_rules_cached, outcome, @@ -25,11 +29,6 @@ use crate::bindings::_helpers::rules::custom_evaluation::{ use crate::bindings::_helpers::rules::custom_hosts::HostCancel; use crate::bindings::_helpers::rules::custom_rule_evidence::verify_custom_rules; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const RULES_MODELS_MODULE: &str = "sqlbuild._native"; -const CUSTOM_PAYLOADS_MODULE: &str = "sqlbuild.rule_engine._helpers.engine.custom_payloads"; const CUSTOM_THREAD: &str = "sqlbuild-custom-rules"; const GENERIC_DIALECT: &str = "generic"; const PROJECT_RULES_OPTIONS: (&[&str], &[&str]) = ( @@ -141,11 +140,11 @@ fn evaluate_project_rules<'py>( let dialect: String = dialect_argument(&arguments)?; let verify_determinism: bool = arguments.flag("verify_determinism")?; let effective: Bound<'py, PyAny> = call_with( - &native(py, "resolve_rule_ignore_selectors")?, + &native(py, NativeAttr::ResolveRuleIgnoreSelectors)?, &[("config", config), ("project", project.clone())], )?; let catalogue: Bound<'py, PyAny> = call_with( - &native(py, "build_rule_catalogue")?, + &native(py, NativeAttr::BuildRuleCatalogue)?, &[ ("config", effective.clone()), ("project_dir", project_dir.clone()), @@ -157,7 +156,7 @@ fn evaluate_project_rules<'py>( } if custom { let rules: Bound<'py, PyAny> = call_with( - &native(py, "select_catalogue_rules")?, + &native(py, NativeAttr::SelectCatalogueRules)?, &[ ("catalogue", catalogue.clone()), ("config", effective.clone()), @@ -252,7 +251,7 @@ pub(crate) fn evaluate_rules<'py>( let graph: Bound<'py, PyAny> = arguments.value("graph")?; let dialect: Bound<'py, PyAny> = arguments.value("dialect")?; let plan: Bound<'py, PyAny> = call_with( - &native(py, "plan_rule_run")?, + &native(py, NativeAttr::PlanRuleRun)?, &[ ("graph", graph.clone()), ("config", arguments.value("config")?), @@ -307,7 +306,7 @@ pub(crate) fn evaluate_rules<'py>( }) })?; let sql_result: PyResult> = call_with( - &native(py, "run_planned_sql_rules")?, + &native(py, NativeAttr::RunPlannedSqlRules)?, &[ ("plan", plan.clone()), ("graph", graph), @@ -322,7 +321,7 @@ pub(crate) fn evaluate_rules<'py>( let sql_result: Bound<'py, PyAny> = sql_result?; let result: Bound<'py, PyAny> = result?; call_with( - &native(py, "finish_rule_run")?, + &native(py, NativeAttr::FinishRuleRun)?, &[ ("plan", plan), ("sql_result", sql_result), @@ -342,7 +341,7 @@ pub(crate) fn prepare_sql_rules<'py>( ) -> PyResult> { let py: Python<'py> = inputs.py(); let preparation: Bound<'py, PyAny> = call_with( - &native(py, "sql_lint_preparation")?, + &native(py, NativeAttr::SqlLintPreparation)?, &[("inputs", inputs.clone()), ("dialect", dialect.clone())], )?; if preparation.is_none() { @@ -351,7 +350,7 @@ pub(crate) fn prepare_sql_rules<'py>( let (project_dir, codes, config): (Bound<'py, PyAny>, Bound<'py, PyAny>, Bound<'py, PyAny>) = preparation.extract()?; let expansions: Bound<'py, PyAny> = - native(py, "early_lint_expansions")?.call1((inputs.getattr("model_inputs")?,))?; + native(py, NativeAttr::EarlyLintExpansions)?.call1((inputs.getattr("model_inputs")?,))?; let work: Bound<'py, PyDict> = PyDict::new(py); work.set_item("project_dir", &project_dir)?; work.set_item("config", config)?; @@ -368,7 +367,7 @@ pub(crate) fn prepare_sql_rules<'py>( Some(&work), )?; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "PreparedSqlLint")?, + ®istry::class(py, RecordId::PreparedSqlLint)?, &[ ("codes", codes), ("project_dir", project_dir), @@ -389,10 +388,10 @@ fn prepare_early_sql_lint<'py>( let project_dir: Bound<'py, PyAny> = arguments.value("project_dir")?; let discovered_inputs: Bound<'py, PyAny> = arguments.value("discovered_inputs")?; let scope: Bound<'py, PyAny> = - native(py, "lint_stop_scope")?.call1((arguments.value("stop")?,))?; + native(py, NativeAttr::LintStopScopeFunction)?.call1((arguments.value("stop")?,))?; within(&scope, |_| { let context: Bound<'py, PyAny> = call_with( - &native(py, "build_lint_expansion_context")?, + &native(py, NativeAttr::BuildLintExpansionContext)?, &[ ("project_dir", project_dir.clone()), ("discovered_inputs", discovered_inputs.clone()), @@ -403,7 +402,7 @@ fn prepare_early_sql_lint<'py>( ], )?; let result: Bound<'py, PyAny> = call_with( - &native(py, "run_lint")?, + &native(py, NativeAttr::RunLint)?, &[ ("project_dir", project_dir.clone()), ("config", arguments.value("config")?), @@ -416,7 +415,7 @@ fn prepare_early_sql_lint<'py>( ], )?; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "PreparedSqlLintResult")?, + ®istry::class(py, RecordId::PreparedSqlLintResult)?, &[("result", result), ("context", context)], ) }) @@ -443,8 +442,8 @@ fn evaluate_native<'py>( (CustomSource::Deferred, Err(_)) => None, }; let (_, request): (Bound<'py, PyAny>, Bound<'py, PyAny>) = prepared?; - let evaluated: PyResult> = - native(py, "evaluate_rules_request").and_then(|evaluate| evaluate.call1((request,))); + let evaluated: PyResult> = native(py, NativeAttr::EvaluateRulesRequest) + .and_then(|evaluate| evaluate.call1((request,))); cancel_on_interrupt(py, &evaluated, &run.cancel); let custom: PyResult> = match custom { Some(handle) => joined(py, handle, &run.cancel), @@ -463,7 +462,7 @@ fn evaluate_native<'py>( .into_any(); if !defer_suppressions { findings = call_with( - &native(py, "finalize_rule_findings_rows")?, + &native(py, NativeAttr::FinalizeRuleFindingsRows)?, &[ ("config", run.config.clone()), ("project_dir", run.project_dir.clone()), @@ -478,7 +477,7 @@ fn evaluate_native<'py>( let custom_hits: i64 = custom.getattr("cache_hits")?.extract()?; let custom_misses: i64 = custom.getattr("cache_misses")?.extract()?; call_with( - &imported(py, RULES_MODELS_MODULE, "RulesResult")?, + ®istry::class(py, RecordId::RulesResult)?, &[ ("findings", findings), ( @@ -521,7 +520,7 @@ fn native_request<'py>( let payloads: Bound<'py, PyAny> = match custom_payloads { Some(payloads) => payloads, None => call_with( - &imported(py, CUSTOM_PAYLOADS_MODULE, "custom_rule_payloads")?, + &host::entry(py, UserCodeHost::CustomRulePayloads)?, &[ ("catalogue", run.catalogue.clone()), ("project_dir", run.project_dir.clone()), @@ -532,7 +531,7 @@ fn native_request<'py>( for code in TYPE_PROOF_RULE_CODES { include_type_proof = include_type_proof || call_with( - &native(py, "rule_selected")?, + &native(py, NativeAttr::RuleSelected)?, &[ ("config", run.config.clone()), ("code", pyo3::types::PyString::new(py, code).into_any()), @@ -541,7 +540,7 @@ fn native_request<'py>( .is_truthy()?; } let request: Bound<'py, PyAny> = call_with( - &native(py, "build_rules_request")?, + &native(py, NativeAttr::BuildRulesRequest)?, &[ ("project", run.project.clone()), ("config", run.config.clone()), @@ -575,7 +574,7 @@ fn start_custom_rules( let selected: Bound<'_, PyFrozenSet> = PyFrozenSet::new( py, call_with( - &native(py, "selected_rule_codes")?, + &native(py, NativeAttr::SelectedRuleCodes)?, &[ ("config", run.config.clone()), ("custom_payloads", payloads.clone()), @@ -683,8 +682,8 @@ fn cancel_on_interrupt(py: Python<'_>, result: &PyResult>, canc } } -fn native<'py>(py: Python<'py>, name: &str) -> PyResult> { - imported(py, NATIVE_MODULE, name) +fn native<'py>(py: Python<'py>, name: NativeAttr) -> PyResult> { + registry::native(py, name) } pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_plan.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_plan.rs index 9efa1652f1..a46162f049 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_plan.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_plan.rs @@ -6,6 +6,10 @@ use pyo3::{pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::boundary::keyword_request::Request; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::catalogue::{ references_custom_rules, resolved_ignore_selectors, rule_catalogue, select_rules, verify_custom_rules, @@ -13,10 +17,6 @@ use crate::bindings::_helpers::rules::catalogue::{ use crate::bindings::_helpers::rules::constants::TYPE_PROOF_RULE_CODES; use crate::bindings::_helpers::rules::rule_run::freed_memory_released; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const CUSTOM_PAYLOADS_MODULE: &str = "sqlbuild.rule_engine._helpers.engine.custom_payloads"; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; const DATACLASSES_MODULE: &str = "dataclasses"; const PATHLIB_MODULE: &str = "pathlib"; @@ -38,7 +38,7 @@ fn plan_rule_run<'py>(options: Option<&Bound<'py, PyDict>>) -> PyResult = PyTuple::new(py, &catalogue)?; let custom_payloads: Bound<'py, PyAny> = call_with( - &imported(py, CUSTOM_PAYLOADS_MODULE, "custom_rule_payloads")?, + &host::entry(py, UserCodeHost::CustomRulePayloads)?, &[ ("catalogue", catalogue_tuple.clone().into_any()), ("project_dir", project_dir.clone()), @@ -100,7 +100,7 @@ fn plan_rule_run<'py>(options: Option<&Bound<'py, PyDict>>) -> PyResult( } } call_with( - &imported(py, NATIVE_MODULE, "project_with")?, + ®istry::native(py, NativeAttr::ProjectWith)?, &[ ("project", project.clone()), ("models", PyTuple::new(py, models)?.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_run.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_run.rs index 02667e3faf..ba678efecb 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_run.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_run.rs @@ -12,12 +12,11 @@ use sqlbuild_rules::rule_results::models::RuleFinding; use crate::bindings::_helpers::boundary::panics::compiler_guard; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::results::rule_finding; use crate::bindings::_helpers::rules::rows::finalize_rule_findings_rows; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const MODELS_MODULE: &str = "sqlbuild._native"; const PATHLIB_MODULE: &str = "pathlib"; const CTYPES_MODULE: &str = "ctypes"; @@ -27,7 +26,7 @@ fn integer(object: &Bound<'_, PyAny>, field: &str) -> PyResult { fn python_finding<'py>(py: Python<'py>, finding: RuleFinding) -> PyResult> { call_with( - &imported(py, MODELS_MODULE, "Finding")?, + ®istry::class(py, RecordId::Finding)?, &[ ("code", finding.code.into_bound_py_any(py)?), ( @@ -96,7 +95,7 @@ fn finish_rule_run<'py>( (integer(result, field)? + integer(sql_result, field)?).into_bound_py_any(py) }; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "RulesRunResult")?, + ®istry::class(py, RecordId::RulesRunResult)?, &[ ("findings", PyTuple::new(py, findings)?.into_any()), ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/rules_config.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/rules_config.rs index b6c0de35f1..87c7f7e583 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/rules_config.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/rules_config.rs @@ -13,34 +13,24 @@ use sqlbuild_rules::configuration::main::load_rules_config::load_rules_config as use sqlbuild_rules::models::RulesConfig; use crate::bindings::_helpers::boundary::panics::compiler_guard; - -const MODELS_MODULE: &str = "sqlbuild._native"; -const ERRORS_MODULE: &str = "sqlbuild._native"; -const NATIVE_MODULE: &str = "sqlbuild._native"; -/// The rules configuration models that stay Python for the custom-rule API. -const PYTHON_CLASSES: &[&str] = &["RulesConfig", "LayoutConfig"]; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; struct Models<'py> { - module: Bound<'py, PyModule>, - native: Bound<'py, PyModule>, + py: Python<'py>, } impl<'py> Models<'py> { fn build( &self, - class: &str, + class: RecordId, fields: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - let arguments: Bound<'py, PyDict> = PyDict::new(self.module.py()); + let arguments: Bound<'py, PyDict> = PyDict::new(self.py); for (name, value) in fields { arguments.set_item(name, value)?; } - let module: &Bound<'py, PyModule> = if PYTHON_CLASSES.contains(&class) { - &self.module - } else { - &self.native - }; - module.getattr(class)?.call((), Some(&arguments)) + registry::class(self.py, class)?.call((), Some(&arguments)) } } @@ -128,13 +118,10 @@ fn tables<'py, T>( } fn config_object<'py>(py: Python<'py>, config: &RulesConfig) -> PyResult> { - let models = Models { - module: py.import(MODELS_MODULE)?, - native: py.import(NATIVE_MODULE)?, - }; + let models = Models { py }; let threshold_overrides = tables(py, &config.threshold_overrides, |item| { models.build( - "ThresholdOverride", + RecordId::ThresholdOverride, &[ ("paths", strings(py, &item.paths)?), ("thresholds", integers(py, &item.thresholds)?), @@ -144,7 +131,7 @@ fn config_object<'py>(py: Python<'py>, config: &RulesConfig) -> PyResult(py: Python<'py>, config: &RulesConfig) -> PyResult(py: Python<'py>, config: &RulesConfig) -> PyResult(py: Python<'py>, config: &RulesConfig) -> PyResult(py: Python<'py>, config: &RulesConfig) -> PyResult(py: Python<'py>, config: &RulesConfig) -> PyResult(py: Python<'py>, config: &RulesConfig) -> PyResult, class: &str, message: String, help: Option) -> PyErr { +fn rules_error(py: Python<'_>, class: ErrorId, message: String, help: Option) -> PyErr { let raised = || -> PyResult { - let error_class: Bound<'_, PyAny> = py.import(ERRORS_MODULE)?.getattr(class)?; + let error_class: Bound<'_, PyAny> = registry::error(py, class)?; let error: Bound<'_, PyAny> = match help { Some(help) => { let arguments: Bound<'_, PyDict> = PyDict::new(py); @@ -258,11 +245,11 @@ pub(crate) fn load_rules_config(py: Python<'_>, project_dir: PathBuf) -> PyResul .canonicalize() .or_else(|_| std::path::absolute(&project_dir))?; let config: RulesConfig = compiler_guard(|| Ok(load_config(&project_dir)))? - .map_err(|message| rules_error(py, "RulesError", message, None))?; + .map_err(|message| rules_error(py, ErrorId::Rules, message, None))?; if let Some(error) = forbidden_exceptions(&config) { return Err(rules_error( py, - "RulesConfigError", + ErrorId::RulesConfig, error.message, Some(error.help), )); @@ -276,9 +263,7 @@ pub(crate) fn load_rules_config(py: Python<'_>, project_dir: PathBuf) -> PyResul fn valid_rules_config(py: Python<'_>, project_dir: PathBuf) -> PyResult>> { match load_rules_config(py, project_dir) { Ok(config) => Ok(Some(config)), - Err(error) if error.is_instance(py, &py.import(ERRORS_MODULE)?.getattr("RulesError")?) => { - Ok(None) - } + Err(error) if error.is_instance(py, ®istry::error(py, ErrorId::Rules)?) => Ok(None), Err(error) => Err(error), } } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/run_lint.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/run_lint.rs index 74cc787ae7..a041f3769e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/run_lint.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/run_lint.rs @@ -14,15 +14,13 @@ use sqlbuild_core::format_command::main::collect_lint_files::collect_lint_files; use sqlbuild_rules::lint_text::main::first_header_only::first_header_only; use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, keywords, + KeywordArguments, call_with, keywords, }; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::lint_expansion::build_lint_expansion_context; use crate::bindings::_helpers::rules::lint_stop::check_stop; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const EXPANSION_MODULE: &str = "sqlbuild._native"; -const LINT_MODELS_MODULE: &str = "sqlbuild._native"; - fn resolved(path: &Path) -> PathBuf { fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf()) } @@ -104,7 +102,7 @@ fn scan_file<'py>( file_path.call_method1("relative_to", (&run.project_dir,))?; let relative_parts: Vec = relative_path.getattr("parts")?.extract()?; let headers: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "scan_headers")?, + ®istry::native(py, NativeAttr::ScanHeaders)?, &[ ("contents", contents.clone()), ( @@ -118,7 +116,7 @@ fn scan_file<'py>( let header_violations: Bound<'py, PyAny> = if run.config.getattr("header_rules_enabled")?.is_truthy()? { call_with( - &imported(py, NATIVE_MODULE, "lint_native_headers")?, + ®istry::native(py, NativeAttr::LintNativeHeaders)?, &[ ("contents", contents.clone()), ("file_path", file_path.clone()), @@ -133,7 +131,7 @@ fn scan_file<'py>( return Ok((header_violations, None)); } let expanded: Bound<'py, PyAny> = call_with( - &imported(py, EXPANSION_MODULE, "expand_file_bodies")?, + ®istry::native(py, NativeAttr::ExpandFileBodies)?, &[ ("file_path", file_path.clone()), ("contents", contents.clone()), @@ -230,7 +228,7 @@ fn lint_files<'py>( } let skip: Bound<'py, PyAny> = PyBool::new(py, run.skip_unexpandable).to_owned().into_any(); let prepared: Bound<'py, PyAny> = call_with( - &imported(py, EXPANSION_MODULE, "prepare_expanded_files")?, + ®istry::native(py, NativeAttr::PrepareExpandedFiles)?, &[ ( "expanded_files", @@ -247,7 +245,7 @@ fn lint_files<'py>( if bodies.is_truthy()? && run.config.getattr("native_enabled")?.is_truthy()? { check_stop(py)?; let body_list: Vec> = bodies.extract()?; - let found: Bound<'py, PyAny> = imported(py, NATIVE_MODULE, "lint_native_sql_bodies")? + let found: Bound<'py, PyAny> = registry::native(py, NativeAttr::LintNativeSqlBodies)? .call1((body_list, files, &run.config))?; for violation in found.try_iter()? { violations.push(violation?); @@ -303,11 +301,11 @@ fn run_lint<'py>( let context: Bound<'py, PyAny> = expansion_context(&run, &arguments)?; let (violations, unexpandable) = lint_files(&run, &files, &context)?; let allow_suppressions: Bound<'py, PyAny> = run.config.getattr("allow_suppressions")?; - let retained: Vec> = imported(py, NATIVE_MODULE, "apply_lint_suppressions")? + let retained: Vec> = registry::native(py, NativeAttr::ApplyLintSuppressions)? .call1((violations, &files, allow_suppressions))? .extract()?; call_with( - &imported(py, LINT_MODELS_MODULE, "LintRunResult")?, + ®istry::class(py, RecordId::LintRunResult)?, &[ ("files_checked", files.len().into_pyobject(py)?.into_any()), ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/selection.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/selection.rs index 1e98140149..9243c00e77 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/selection.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/selection.rs @@ -9,9 +9,10 @@ use pyo3::{pyfunction, wrap_pyfunction}; use sqlbuild_rules::rules::main::selected_codes::selected_codes_json; use crate::bindings::_helpers::boundary::panics::compiler_guard; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::constants::RULES_NATIVE_API_VERSION; -const RULES_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; const DATACLASSES_MODULE: &str = "dataclasses"; const JSON_MODULE: &str = "json"; const BUILTINS_MODULE: &str = "builtins"; @@ -81,10 +82,7 @@ fn rule_selected(config: &Bound<'_, PyAny>, code: &str) -> PyResult { /// `RulesError(message)`, caused by `cause` when given. pub(crate) fn rules_error(py: Python<'_>, message: &str, cause: Option) -> PyErr { let raised = || -> PyResult { - let error: Bound<'_, PyAny> = py - .import(RULES_EXCEPTIONS_MODULE)? - .getattr("RulesError")? - .call1((message,))?; + let error: Bound<'_, PyAny> = registry::error(py, ErrorId::Rules)?.call1((message,))?; Ok(PyErr::from_value(error)) }; let error: PyErr = raised().unwrap_or_else(|error| error); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/sql_rules.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/sql_rules.rs index d8cf5699fb..6d9ca62c97 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/sql_rules.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/sql_rules.rs @@ -12,14 +12,11 @@ use pyo3::{IntoPyObjectExt, pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::boundary::keyword_request::Request; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::constants::SAFE_RULE_FIX_CODES; use crate::bindings::_helpers::rules::early_sql_lint::{sql_lint_config, sql_rule_cache_path}; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const RULES_MODELS_MODULE: &str = "sqlbuild._native"; -const LINT_EXCEPTIONS_MODULE: &str = "sqlbuild._native"; const DATACLASSES_MODULE: &str = "dataclasses"; const JSON_MODULE: &str = "json"; const BUILTINS_MODULE: &str = "builtins"; @@ -28,8 +25,8 @@ const SQL_RULE_SUPPRESSION_CODE: &str = "SQBRSQL000"; const DEFAULT_REMEDIATION: &str = "Update the authored SQL to satisfy this rule."; /// `native.name`, looked up at call time. -fn native<'py>(py: Python<'py>, name: &str) -> PyResult> { - imported(py, NATIVE_MODULE, name) +fn native<'py>(py: Python<'py>, name: NativeAttr) -> PyResult> { + registry::native(py, name) } /// The SQL rule codes among `rules`, in order. @@ -79,7 +76,7 @@ fn run_planned_sql_rules<'py>(options: Option<&Bound<'py, PyDict>>) -> PyResult< &codes, &effective_config.getattr("thresholds")?, ( - native(py, "compiled_relation_keys")?.call1((graph.getattr("project")?,))?, + native(py, NativeAttr::CompiledRelationKeys)?.call1((graph.getattr("project")?,))?, effective_config.getattr("allow_exceptions")?, ), )?; @@ -112,7 +109,7 @@ fn run_planned_sql_rules<'py>(options: Option<&Bound<'py, PyDict>>) -> PyResult< evaluated_sql_rules(&run, (&lint_key, identities), project_files)?; let sql_ms: i64 = (started.elapsed().as_secs_f64() * 1000.0).round_ties_even() as i64; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "SqlRulesEvaluation")?, + ®istry::class(py, RecordId::SqlRulesEvaluation)?, &[ ("findings", findings), ("cache_hits", hits.into_bound_py_any(py)?), @@ -124,7 +121,7 @@ fn run_planned_sql_rules<'py>(options: Option<&Bound<'py, PyDict>>) -> PyResult< fn lint_key(lint_config: &Bound<'_, PyAny>) -> PyResult { call_with( - &native(lint_config.py(), "sql_rule_lint_key")?, + &native(lint_config.py(), NativeAttr::SqlRuleLintKey)?, &[("config", lint_config.clone())], )? .extract() @@ -133,7 +130,7 @@ fn lint_key(lint_config: &Bound<'_, PyAny>) -> PyResult { fn collected_project_files<'py>(project_dir: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = project_dir.py(); call_with( - &native(py, "collect_lint_project_files")?, + &native(py, NativeAttr::CollectLintProjectFiles)?, &[ ("project_dir", project_dir.clone()), ("selected_paths", py.None().into_bound(py)), @@ -195,7 +192,7 @@ fn model_rule_identities(run: &SqlRun<'_, '_>, lint_key: &str) -> PyResult = call_with( - &native(py, "sql_model_rule_keys")?, + &native(py, NativeAttr::SqlModelRuleKeys)?, &[ ("lint_key", PyString::new(py, lint_key).into_any()), ("codes", PyList::new(py, run.codes)?.into_any()), @@ -238,7 +235,7 @@ fn cached_findings<'py>( ) -> PyResult> { let py: Python<'py> = bucket.py(); Ok(call_with( - &native(py, "lookup_sql_rule_cache")?, + &native(py, NativeAttr::LookupSqlRuleCache)?, &[ ("entries", bucket.clone()), ("keys", keys.into_bound_py_any(py)?), @@ -296,7 +293,7 @@ fn evaluated_sql_rules<'py>( let cache_path: Bound<'py, PyAny> = sql_rule_cache_path(project_dir)?; let bucket: Bound<'py, PyAny> = if run.cache_enabled { call_with( - &native(py, "load_sql_rule_cache")?, + &native(py, NativeAttr::LoadSqlRuleCache)?, &[("path", cache_path.clone())], )? } else { @@ -345,7 +342,7 @@ fn evaluated_sql_rules<'py>( .call_method0("as_posix")? .extract()?; let key: Option = call_with( - &native(py, "sql_file_rule_key")?, + &native(py, NativeAttr::SqlFileRuleKey)?, &[ ("lint_key", PyString::new(py, lint_key).into_any()), ("codes", PyList::new(py, run.codes)?.into_any()), @@ -418,7 +415,7 @@ fn evaluated_sql_rules<'py>( write_cache(&bucket, (&cache_path, written), &evaluated)?; } let hinted: Bound<'py, PyAny> = call_with( - &native(py, "with_duplicate_literal_hints")?, + &native(py, NativeAttr::WithDuplicateLiteralHints)?, &[ ("findings", PyTuple::new(py, findings.iter())?.into_any()), ("project_dir", project_dir.clone()), @@ -434,7 +431,7 @@ fn write_cache<'py>( evaluated: &[Bound<'py, PyAny>], ) -> PyResult<()> { let py: Python<'py> = bucket.py(); - let encode: Bound<'py, PyAny> = native(py, "encode_sql_rule_findings")?; + let encode: Bound<'py, PyAny> = native(py, NativeAttr::EncodeSqlRuleFindings)?; let mut by_path: HashMap>> = HashMap::new(); for finding in evaluated { let path: String = finding @@ -450,7 +447,7 @@ fn write_cache<'py>( bucket.set_item(path, (identity, encoded))?; } let _ = call_with( - &native(py, "write_sql_rule_cache")?, + &native(py, NativeAttr::WriteSqlRuleCache)?, &[("path", cache_path.clone()), ("entries", bucket.clone())], )?; Ok(()) @@ -464,7 +461,7 @@ fn prepared_lint_violations<'py>( source_files: &Bound<'py, PyAny>, ) -> PyResult>> { let py: Python<'py> = run.project_dir.py(); - let run_lint: Bound<'py, PyAny> = native(py, "run_lint")?; + let run_lint: Bound<'py, PyAny> = native(py, NativeAttr::RunLint)?; let compiled_expansions: Bound<'py, PyAny> = run.project.getattr("sql_expansions")?; let enabled_codes: Bound<'py, PyAny> = run.lint_config.getattr("enabled_native_rules")?; let enabled: Vec = if enabled_codes.is_none() { @@ -551,7 +548,7 @@ fn lint_expansion_context<'py>(run: &SqlRun<'_, 'py>) -> PyResult( model_paths: &BTreeSet, ) -> PyResult>> { let py: Python<'py> = run.project_dir.py(); - let engine_error: String = imported(py, LINT_EXCEPTIONS_MODULE, "NativeLintError")? + let engine_error: String = registry::error(py, ErrorId::NativeLint)? .getattr("code")? .extract()?; let safe_fixes: Bound<'py, PyAny> = PyFrozenSet::new(py, SAFE_RULE_FIX_CODES)?.into_any(); @@ -637,7 +634,7 @@ fn lint_finding<'py>( && safe_fixes.contains(&code)? && fixable_paths.contains(&path.call_method0("as_posix")?.extract::()?); call_with( - &imported(py, RULES_MODELS_MODULE, "Finding")?, + ®istry::class(py, RecordId::Finding)?, &[ ("code", code), ("path", path), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/sql_test_objects.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/sql_test_objects.rs index cd2e98c6ce..903a68e7d3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/sql_test_objects.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/sql_test_objects.rs @@ -13,9 +13,9 @@ use sqlbuild_rules::models::{ use crate::bindings::_helpers::boundary::panics::value_error; use crate::bindings::_helpers::compiled_project::records::plain_typed_value; use crate::bindings::_helpers::compiled_project::values::plain_value; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; -const MODEL_PAYLOAD_CLASS: &str = "CompiledModelSqlTestPayload"; const DESCRIPTION_KEY: &str = "description"; fn posix(value: &Bound<'_, PyAny>) -> PyResult { @@ -87,10 +87,8 @@ fn tested_resources(test: &Bound<'_, PyAny>) -> PyResult> { fn with_payload(test: &Bound<'_, PyAny>, mut fact: SqlTestFact) -> PyResult { let payload: Bound<'_, PyAny> = test.getattr("payload")?; - let model_payload: Bound<'_, PyAny> = test - .py() - .import(NATIVE_RECORDS_MODULE)? - .getattr(MODEL_PAYLOAD_CLASS)?; + let model_payload: Bound<'_, PyAny> = + registry::class(test.py(), RecordId::CompiledModelSqlTestPayload)?; if !payload.is_instance(&model_payload)? { return Ok(fact); } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/capture_command.rs b/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/capture_command.rs index 27b0c40a09..833b74060d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/capture_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/capture_command.rs @@ -9,7 +9,9 @@ use sqlbuild_core::scenario_command::main::snapshot_capture_warning::snapshot_ca use sqlbuild_core::scenario_command::main::sql_analysis_required_error::sql_analysis_required_error; use sqlbuild_core::scenario_command::models::SqlAnalysisCommand; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::scenario_command::conversions::succeeded; use crate::bindings::_helpers::scenario_command::presentation::{ CompletionKind, SectionStatus, activity, begin_section, connection_hooks, progress_relay, @@ -22,7 +24,6 @@ use crate::bindings::_helpers::scenario_command::setup::{ write, write_headers, }; -const EXECUTOR_RUN_MODULE: &str = "sqlbuild.executor.pipeline.main.run"; const EXECUTION_COMMAND: &str = "sqb scenario capture"; fn count(label: &str, value: usize) -> SummaryCount { @@ -106,7 +107,7 @@ fn run_scenario_capture_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> P ), )?; let results: Vec> = call_with( - &imported(py, EXECUTOR_RUN_MODULE, "run_scenario_capture_pipeline")?, + &host::entry(py, ExecutorHost::RunScenarioCapturePipeline)?, &[ ("project_dir", project.project_dir.clone()), ("pipeline_result", pipeline), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/conversions.rs b/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/conversions.rs index 67c07cfe32..997eebc2fe 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/conversions.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/conversions.rs @@ -15,7 +15,11 @@ use sqlbuild_core::scenario_command::models::{ NamedStatements, RetainedInputs, RetainedRelation, ScenarioResultRow, WarehouseRuntimeInputs, }; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; const SUCCESS_STATUS: &str = "success"; const FAILED_STATUS: &str = "failed"; @@ -25,9 +29,6 @@ const FAIL_STATUS: &str = "FAIL"; const RUN_DIR: &str = "run"; const SCENARIOS_DIR: &str = "scenarios"; const LOCAL_DIR: &str = "local"; -const PLANNER_TYPES: &str = "sqlbuild._native"; -const QUALIFIED_NAME_MODULE: &str = - "sqlbuild.adapter.relations.main.resolve_relation_location_qualified_name"; fn items<'py>(value: &Bound<'py, PyAny>) -> PyResult>> { value.try_iter()?.collect() @@ -91,7 +92,7 @@ fn assertion_row(assertion: &Bound<'_, PyAny>) -> PyResult { fn retained_relations(relation_map: &Bound<'_, PyAny>) -> PyResult> { let py: Python<'_> = relation_map.py(); - let kinds: Bound<'_, PyAny> = imported(py, PLANNER_TYPES, "ScenarioArtifactKind")?; + let kinds: Bound<'_, PyAny> = registry::class(py, RecordId::ScenarioArtifactKind)?; items(&relation_map.getattr("artifacts")?)? .iter() .map(|artifact| { @@ -250,11 +251,7 @@ fn expectation_sql( let mut expectations: ExpectationSql = ExpectationSql::default(); for expected in items(&scenario_plan.getattr("expected_expectations")?)? { let actual_relation: String = call_with( - &imported( - py, - QUALIFIED_NAME_MODULE, - "resolve_relation_location_qualified_name", - )?, + &host::entry(py, AdapterHost::ResolveRelationLocationQualifiedName)?, &[ ("adapter", adapter.clone()), ("location", expected.getattr("actual_destination")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/presentation.rs b/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/presentation.rs index 89fd26f640..a699b2b376 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/presentation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/presentation.rs @@ -13,16 +13,18 @@ use sqlbuild_core::scenario_command::main::scenario_activity_message::scenario_a use sqlbuild_core::scenario_command::models::ScenarioNamespace; use crate::bindings::_helpers::build_progress::transient_status::status_reporter; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::scenario_command::conversions::{ capture_row, scenario_result_row, write_local_runtime_target, write_warehouse_runtime_target, }; use crate::bindings::_helpers::scenario_command::setup::{ - NATIVE_MODULE, boolean, flush, stream_isatty, text, write, + boolean, flush, stream_isatty, text, write, }; -const RUNTIME_CONTRACT_MODELS: &str = "sqlbuild.runtime.contracts.models"; - /// How a finished scenario is reported. #[derive(Clone, Debug)] pub(crate) enum CompletionKind { @@ -159,7 +161,7 @@ pub(crate) fn connection_hooks<'py>( ) -> PyResult> { let py: Python<'py> = stream.py(); let reporter: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "ConnectionProgressReporter")?, + ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, &[ ("adapter_name", text(py, adapter_name)), ("blank_line_after_complete", boolean(py, true)), @@ -174,7 +176,7 @@ pub(crate) fn connection_hooks<'py>( }, )?; call_with( - &imported(py, RUNTIME_CONTRACT_MODELS, "ConnectionHooks")?, + &host::entry(py, ExecutorHost::ConnectionHooks)?, &[ ( "on_connection_start", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/setup.rs b/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/setup.rs index 2dfdfeddb7..91e0b19097 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/setup.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/setup.rs @@ -21,20 +21,14 @@ use sqlbuild_core::scenario_command::models::{ }; use crate::bindings::_helpers::command_runtime::adapters::resolve_cli_adapter; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, within}; +use crate::bindings::_helpers::compile_driver::python_calls::{call_with, within}; use crate::bindings::_helpers::discovery::guidance::sql_analysis_guidance; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -pub(crate) const NATIVE_MODULE: &str = "sqlbuild._native"; -const ERRORS_MODULE: &str = "sqlbuild._native"; -const REJECT_UNIT_TESTS_MODULE: &str = "sqlbuild._native"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; -const SCENARIO_CONFIG_MODULE: &str = "sqlbuild._native"; -const EXTERNAL_REFS_MODULE: &str = "sqlbuild._native"; -const LIFECYCLE_MODULE: &str = "sqlbuild.runtime.observability.classes.operation_lifecycle"; -const SCENARIO_MODELS: &str = "sqlbuild.executor.scenario.models"; const METADATA_MODULE: &str = "importlib.metadata"; const DISTRIBUTION_NAME: &str = "sqlbuild"; const UNKNOWN_VERSION: &str = "unknown"; @@ -53,7 +47,7 @@ pub(crate) fn cli_user_error(py: Python<'_>, error: ScenarioCommandError) -> PyE let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("code", error.code)?; options.set_item("help", error.help)?; - imported(py, ERRORS_MODULE, "CliUserError")?.call((error.message,), Some(&options)) + registry::error(py, ErrorId::CliUser)?.call((error.message,), Some(&options)) })(); match raised { Ok(error) => PyErr::from_value(error), @@ -120,13 +114,13 @@ pub(crate) fn discover_project<'py>( _ => py.import("pathlib")?.getattr("Path")?.call_method0("cwd")?, }; let discovered: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "discover_project_inputs")?, + ®istry::native(py, NativeAttr::DiscoverProjectInputs)?, &[("project_dir", project_dir.clone())], )?; let selectors: Bound<'py, PyAny> = request.getattr("selectors")?; let exclude: Bound<'py, PyAny> = request.getattr("exclude")?; call_with( - &imported(py, REJECT_UNIT_TESTS_MODULE, "reject_unit_test_selectors")?, + ®istry::native(py, NativeAttr::RejectUnitTestSelectors)?, &[ ("select", selectors.clone()), ("exclude", exclude.clone()), @@ -192,7 +186,7 @@ pub(crate) fn require_sql_analysis( pub(crate) fn project_adapter_name(project: &ScenarioProject<'_>) -> PyResult { let py: Python<'_> = project.discovered.py(); call_with( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ( "project_config", @@ -227,7 +221,7 @@ pub(crate) fn project_connection_config<'py>( ) -> PyResult> { let py: Python<'py> = project.discovered.py(); call_with( - &imported(py, NATIVE_MODULE, "resolve_project_connection_config")?, + ®istry::native(py, NativeAttr::ResolveProjectConnectionConfig)?, &[ ("discovered_inputs", project.discovered.clone()), ("project_dir", project.project_dir.clone()), @@ -252,7 +246,7 @@ pub(crate) fn write_headers<'py>( write(stream, &render_namespace_header(&project.namespace))?; let selectors: Vec = project.selectors.extract()?; let planning: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "PlanningProgressReporter")?, + ®istry::native(py, NativeAttr::PlanningProgressReporter)?, &[ ("stream", stream.clone()), ("use_color", boolean(py, style.use_color)), @@ -261,7 +255,7 @@ pub(crate) fn write_headers<'py>( write(stream, "\n")?; let target: Option = (!selectors.is_empty()).then(|| selectors.join(" ")); call_with( - &imported(py, NATIVE_MODULE, "write_execution_header")?, + ®istry::native(py, NativeAttr::WriteExecutionHeader)?, &[ ("stream", stream.clone()), ("command", text(py, command)), @@ -292,11 +286,7 @@ pub(crate) fn compile<'py>( } = inputs; let py: Python<'py> = project.discovered.py(); let resolver: Bound<'py, PyAny> = call_with( - &imported( - py, - EXTERNAL_REFS_MODULE, - "resolve_external_sql_reference_resolver", - )?, + ®istry::native(py, NativeAttr::ResolveExternalSqlReferenceResolver)?, &[ ("project_dir", project.project_dir.clone()), ("discovered_inputs", project.discovered.clone()), @@ -305,11 +295,11 @@ pub(crate) fn compile<'py>( let mut pairs: Vec<(&str, Bound<'py, PyAny>)> = options.to_vec(); pairs.push(("external_sql_reference_resolver", resolver)); let options: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "CompilePipelineOptions")?, + ®istry::class(py, RecordId::CompilePipelineOptions)?, &pairs, )?; let lifecycle: Bound<'py, PyAny> = call_with( - &imported(py, LIFECYCLE_MODULE, "OperationLifecycle")?, + &host::entry(py, ExecutorHost::OperationLifecycle)?, &[ ("operation_kind", text(py, "project")), ("operation_name", text(py, "project_compile")), @@ -317,7 +307,7 @@ pub(crate) fn compile<'py>( )?; within(&lifecycle, |_| { call_with( - &imported(py, NATIVE_MODULE, "run_compile_only_pipeline")?, + ®istry::native(py, NativeAttr::RunCompileOnlyPipeline)?, &[ ("discovered_inputs", project.discovered.clone()), ("adapter", adapter.clone()), @@ -373,11 +363,7 @@ pub(crate) fn capture_limits<'py>( let py: Python<'py> = request.py(); let inputs: Bound<'py, PyAny> = request.getattr("limit_inputs")?; let configured: Bound<'py, PyAny> = call_with( - &imported( - py, - SCENARIO_CONFIG_MODULE, - "resolve_effective_scenario_config", - )?, + ®istry::native(py, NativeAttr::ResolveEffectiveScenarioConfig)?, &[ ( "project_config", @@ -403,7 +389,7 @@ pub(crate) fn capture_limits<'py>( }, ); call_with( - &imported(py, SCENARIO_MODELS, "ScenarioSnapshotCaptureLimits")?, + &host::entry(py, ExecutorHost::ScenarioSnapshotCaptureLimits)?, &[ ( "max_rows_per_relation", @@ -443,7 +429,7 @@ pub(crate) fn capture_settings<'py>( i64::try_from(elapsed.as_secs()).unwrap_or(i64::MAX) }); call_with( - &imported(py, SCENARIO_MODELS, "ScenarioCaptureSettings")?, + &host::entry(py, ExecutorHost::ScenarioCaptureSettings)?, &[ ("captured_at", text(py, &format_captured_at(epoch_seconds))), ("capture_adapter", text(py, capture_adapter)), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/snapshot_sync.rs b/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/snapshot_sync.rs index c58c3c8270..dc35415aa3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/snapshot_sync.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/snapshot_sync.rs @@ -7,19 +7,21 @@ use sqlbuild_core::cli_text::models::SummaryCount; use sqlbuild_core::scenario_command::main::snapshot_capture_warning::snapshot_capture_warning; use sqlbuild_core::scenario_command::models::ScenarioNamespace; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::scenario_command::conversions::succeeded; use crate::bindings::_helpers::scenario_command::presentation::{ CompletionKind, SectionStatus, begin_section, connection_hooks, progress_relay, }; use crate::bindings::_helpers::scenario_command::setup::{ - CompileInputs, NATIVE_MODULE, boolean, capture_limits, capture_settings, compile, flush, - optional_text, project_connection_config, selected_scenarios, text, write, + CompileInputs, boolean, capture_limits, capture_settings, compile, flush, optional_text, + project_connection_config, selected_scenarios, text, write, }; use crate::bindings::_helpers::scenario_command::test_command::TestRun; -const EXECUTOR_RUN_MODULE: &str = "sqlbuild.executor.pipeline.main.run"; - /// The outcome of syncing local snapshots: its exit code and capture results. pub(crate) struct LocalSync<'py> { pub(crate) exit_code: i32, @@ -36,7 +38,7 @@ impl LocalSync<'_> { ) -> PyResult { let py: Python<'_> = request.py(); let payload: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "format_scenario_snapshot_execution_json")?, + ®istry::native(py, NativeAttr::FormatScenarioSnapshotExecutionJson)?, &[ ( "run_namespace", @@ -48,7 +50,7 @@ impl LocalSync<'_> { ], )?; call_with( - &imported(py, NATIVE_MODULE, "write_execution_json_output")?, + ®istry::native(py, NativeAttr::WriteExecutionJsonOutput)?, &[ ("payload", payload), ("json_output", request.getattr("json_output")?), @@ -81,11 +83,7 @@ pub(crate) fn sync_local_snapshots<'py>( .getattr("project_config")? .getattr("name")?; let names: Vec = call_with( - &imported( - py, - EXECUTOR_RUN_MODULE, - "select_scenario_snapshot_capture_candidates", - )?, + &host::entry(py, ExecutorHost::SelectScenarioSnapshotCaptureCandidates)?, &[ ("project_dir", run.project.project_dir.clone()), ("pipeline_result", local_pipeline.clone()), @@ -112,7 +110,7 @@ pub(crate) fn sync_local_snapshots<'py>( }); } let planning: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "PlanningProgressReporter")?, + ®istry::native(py, NativeAttr::PlanningProgressReporter)?, &[ ("stream", run.stream.clone()), ("use_color", boolean(py, run.style.use_color)), @@ -172,7 +170,7 @@ pub(crate) fn sync_local_snapshots<'py>( let results: Bound<'py, PyTuple> = PyTuple::new( py, call_with( - &imported(py, EXECUTOR_RUN_MODULE, "run_scenario_capture_pipeline")?, + &host::entry(py, ExecutorHost::RunScenarioCapturePipeline)?, &[ ("project_dir", run.project.project_dir.clone()), ("pipeline_result", pipeline), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/test_command.rs b/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/test_command.rs index 05eefdb218..624d65d4bd 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/test_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/test_command.rs @@ -13,23 +13,23 @@ use sqlbuild_core::scenario_command::models::{ ScenarioNamespace, ScenarioTestFlags, SqlAnalysisCommand, }; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::scenario_command::conversions::succeeded; use crate::bindings::_helpers::scenario_command::presentation::{ CompletionKind, SectionStatus, activity, begin_section, connection_hooks, progress_relay, write_completion, }; use crate::bindings::_helpers::scenario_command::setup::{ - CompileInputs, NATIVE_MODULE, ScenarioProject, boolean, cli_user_error, compile, - discover_project, flush, int, optional_text, project_adapter_name, project_connection_config, - require_capture_dialect, require_sql_analysis, resolve_adapter, selected_scenarios, style, - text, write, write_headers, + CompileInputs, ScenarioProject, boolean, cli_user_error, compile, discover_project, flush, int, + optional_text, project_adapter_name, project_connection_config, require_capture_dialect, + require_sql_analysis, resolve_adapter, selected_scenarios, style, text, write, write_headers, }; use crate::bindings::_helpers::scenario_command::snapshot_sync::{LocalSync, sync_local_snapshots}; -const EFFECTIVE_SETTINGS_MODULE: &str = "sqlbuild._native"; -const EXECUTOR_RUN_MODULE: &str = "sqlbuild.executor.pipeline.main.run"; -const SCENARIO_MODELS: &str = "sqlbuild.executor.scenario.models"; const LOCAL_ADAPTER: &str = "duckdb"; const LOCAL_STATUSES: [&str; 4] = ["PASS", "FAIL", "ERROR", "SKIP"]; const TARGET_DIR: &str = "target"; @@ -96,11 +96,7 @@ fn prepare<'py>(py: Python<'py>, request: &Bound<'py, PyAny>) -> PyResult = request.getattr("concurrency")?; let concurrency: i64 = if requested.is_none() { call_with( - &imported( - py, - EFFECTIVE_SETTINGS_MODULE, - "build_effective_settings_config", - )?, + ®istry::native(py, NativeAttr::BuildEffectiveSettingsConfig)?, &[("discovered_inputs", project.discovered.clone())], )? .getattr("concurrency")? @@ -150,7 +146,7 @@ fn finish( write_completion(&run.stream, (&run.project.namespace, succeeded))?; let namespace: &ScenarioNamespace = &run.project.namespace; let payload: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "format_scenario_execution_json")?, + ®istry::native(py, NativeAttr::FormatScenarioExecutionJson)?, &[ ("results", results.clone().into_any()), ("local", boolean(py, run.local)), @@ -166,7 +162,7 @@ fn finish( ], )?; call_with( - &imported(py, NATIVE_MODULE, "write_execution_json_output")?, + ®istry::native(py, NativeAttr::WriteExecutionJsonOutput)?, &[ ("payload", payload), ("json_output", request.getattr("json_output")?), @@ -231,9 +227,9 @@ fn run_scenarios( relay.getattr("on_scenario_complete")?, ), ]; - let runner: &str = if run.local { + let runner: ExecutorHost = if run.local { let replay_source: Bound<'_, PyAny> = call_with( - &imported(py, SCENARIO_MODELS, "ScenarioLocalReplaySource")?, + &host::entry(py, ExecutorHost::ScenarioLocalReplaySource)?, &[ ( "lexical_syntax", @@ -248,7 +244,7 @@ fn run_scenarios( ("strict", request.getattr("strict")?), ("replay_source", replay_source), ]); - "run_scenario_local_test_pipeline" + ExecutorHost::RunScenarioLocalTestPipeline } else { arguments.extend([ ("connection_config", run.connection_config.clone()), @@ -259,11 +255,11 @@ fn run_scenarios( connection_hooks(&run.stream, (&run.adapter_name, run.style))?, ), ]); - "run_scenario_test_pipeline" + ExecutorHost::RunScenarioTestPipeline }; let results: Bound<'_, PyTuple> = PyTuple::new( py, - call_with(&imported(py, EXECUTOR_RUN_MODULE, runner)?, &arguments)? + call_with(&host::entry(py, runner)?, &arguments)? .try_iter()? .collect::>>()?, )?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scope_records/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/scope_records/constants.rs index 1e77649b84..103028b1ff 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scope_records/constants.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scope_records/constants.rs @@ -1,5 +1,6 @@ //! Layouts of the declaration scope value records hosted natively. +use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::records::models::{FieldDefault, FieldSpec, RecordSpec}; const R_RESOURCE_IDENTITY: RecordSpec = RecordSpec { @@ -23,7 +24,7 @@ const R_OWNERSHIP_ROOT: RecordSpec = RecordSpec { FieldSpec::required("path"), FieldSpec::new( "kind", - FieldDefault::Member("sqlbuild._native", "OwnershipRootKind", "RESOURCE"), + FieldDefault::NativeMember(RecordId::OwnershipRootKind, "RESOURCE"), ), FieldSpec::new("resource_kind", FieldDefault::None), ], @@ -94,7 +95,7 @@ const R_USAGE_RECORD: RecordSpec = RecordSpec { FieldSpec::required("declaration"), FieldSpec::new( "kind", - FieldDefault::Member("sqlbuild._native", "UsageKind", "RUNTIME"), + FieldDefault::NativeMember(RecordId::UsageKind, "RUNTIME"), ), FieldSpec::new("through", FieldDefault::None), FieldSpec::new("enum_member", FieldDefault::None), @@ -109,7 +110,7 @@ const R_GRANT_RECORD: RecordSpec = RecordSpec { FieldSpec::required("through"), FieldSpec::new( "kind", - FieldDefault::Member("sqlbuild._native", "GrantKind", "EXPECTED_MODEL"), + FieldDefault::NativeMember(RecordId::GrantKind, "EXPECTED_MODEL"), ), ], }; @@ -140,7 +141,7 @@ const R_SCOPE_DIAGNOSTIC: RecordSpec = RecordSpec { FieldSpec::required("message"), FieldSpec::new( "severity", - FieldDefault::Member("sqlbuild._native", "ScopeDiagnosticSeverity", "ERROR"), + FieldDefault::NativeMember(RecordId::ScopeDiagnosticSeverity, "ERROR"), ), FieldSpec::new("path", FieldDefault::None), FieldSpec::new("line", FieldDefault::None), @@ -175,7 +176,7 @@ const R_SCOPE_INDEX: RecordSpec = RecordSpec { FieldSpec::new("diagnostics", FieldDefault::EmptyTuple), FieldSpec::new( "completeness", - FieldDefault::Call("sqlbuild._native", "ScopeCompleteness"), + FieldDefault::NativeCall(RecordId::ScopeCompleteness), ), ], }; @@ -402,7 +403,7 @@ const R_SCOPE_REPORT: RecordSpec = RecordSpec { FieldSpec::new("nearby_unavailable", FieldDefault::EmptyTuple), FieldSpec::new( "filters", - FieldDefault::Call("sqlbuild._native", "ScopeReportFilters"), + FieldDefault::NativeCall(RecordId::ScopeReportFilters), ), FieldSpec::new("sections", FieldDefault::EmptyTuple), FieldSpec::new("explanation", FieldDefault::None), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scopes/compile_usages.rs b/crates/sqlbuild-python/src/bindings/_helpers/scopes/compile_usages.rs index ec33f64641..a72d78c342 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/compile_usages.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/compile_usages.rs @@ -19,11 +19,9 @@ use sqlbuild_scopes::scope_index::models::{ }; use crate::bindings::_helpers::compiled_project::model_loop_named::scope_path_message; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const SCOPE_ERRORS_MODULE: &str = "sqlbuild._native"; -const SCOPE_MODELS_MODULE: &str = "sqlbuild._native"; -const COLLECTOR_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const INACCESSIBLE_DECLARATION_CODE: &str = "S006"; const SCHEMA_USAGE_KINDS: [&str; 2] = ["schema", "enum"]; @@ -145,13 +143,11 @@ pub(crate) fn model_schema_scope_usages<'py>( let (usages, diagnostics, error) = scope_schema_usages(schemas, record_rows)?; if let Some(message) = error { return Err(PyErr::from_value( - py.import(SCOPE_ERRORS_MODULE)? - .getattr("InvalidScopePathError")? - .call1((message,))?, + registry::error(py, ErrorId::InvalidScopePath)?.call1((message,))?, )); } report_inaccessible(py, diagnostics)?; - let usage_record = py.import(SCOPE_MODELS_MODULE)?.getattr("UsageRecord")?; + let usage_record = registry::class(py, RecordId::UsageRecord)?; let mut built: Vec> = Vec::with_capacity(usages.len()); for (consumer, used) in usages { let fields: Bound<'py, PyDict> = PyDict::new(py); @@ -190,12 +186,8 @@ fn report_inaccessible(py: Python<'_>, diagnostics: Vec) -> PyRes if diagnostics.is_empty() { return Ok(()); } - let report = py - .import(COLLECTOR_MODULE)? - .getattr("report_compile_diagnostic")?; - let compiler_diagnostic = py - .import(COMPILE_MODELS_MODULE)? - .getattr("CompilerDiagnostic")?; + let report = registry::native(py, NativeAttr::ReportCompileDiagnostic)?; + let compiler_diagnostic = registry::class(py, RecordId::CompilerDiagnostic)?; let path_class = py.import("pathlib")?.getattr("Path")?; for (key, message, path, help) in diagnostics { let fields: Bound<'_, PyDict> = PyDict::new(py); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scopes/relationship_names.rs b/crates/sqlbuild-python/src/bindings/_helpers/scopes/relationship_names.rs index 9ac4b234e4..ecce43864f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/relationship_names.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/relationship_names.rs @@ -13,13 +13,11 @@ use sqlbuild_scopes::relationship_names::models::{ use sqlbuild_sqltext::sql_scan::models::LexicalSyntax; use crate::bindings::_helpers::boundary::panics::compiler_error; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sqltext::lexical_syntax::LexicalSyntaxInput; use crate::bindings::types::CompilerDetach; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; - /// One scan's error, or its values. type Scanned = (Option, Vec); @@ -105,16 +103,12 @@ fn extract_sql_test_ctes<'py>( let ctes: Vec<(String, String)> = match outcome { TopLevelCtes::Scanned(ctes) => ctes, TopLevelCtes::Failed(message) => { - let error: Bound<'py, PyAny> = py - .import(COMPILE_ERRORS_MODULE)? - .getattr("CompileInputError")? - .call1((message,))?; + let error: Bound<'py, PyAny> = + registry::error(py, ErrorId::CompileInput)?.call1((message,))?; return Err(PyErr::from_value(error)); } }; - let cte_class: Bound<'py, PyAny> = py - .import(NATIVE_RECORDS_MODULE)? - .getattr("CompileSqlTestCte")?; + let cte_class: Bound<'py, PyAny> = registry::class(py, RecordId::CompileSqlTestCte)?; let mut built: Vec> = Vec::with_capacity(ctes.len()); for (name, body) in ctes { let arguments: Bound<'py, PyDict> = PyDict::new(py); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_command.rs b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_command.rs index 817073f23a..f9ce48b8b1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_command.rs @@ -14,6 +14,8 @@ use sqlbuild_scopes::scope_text::main::scope_result_failed::scope_result_failed; use sqlbuild_scopes::scope_text::models::ScopeTextRequest; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::scopes::scope_lookup::build_scope_lookup; use crate::bindings::_helpers::scopes::scope_objects::ScopeClasses; use crate::bindings::_helpers::scopes::scope_report_objects::read_result; @@ -22,10 +24,6 @@ use crate::bindings::_helpers::scopes::scope_reports::{ serialize_scope_report, }; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const MODELS_MODULE: &str = "sqlbuild._native"; -const TYPES_MODULE: &str = "sqlbuild._native"; -const ERRORS_MODULE: &str = "sqlbuild._native"; const TARGET_ERROR_CODE: &str = "C960"; const BARE_USAGE: &str = "bare names work for models, sources, seeds and functions; write macros, \ enums, constants, tests and audits with their prefix, for example macro:"; @@ -48,10 +46,8 @@ fn user_error(py: Python<'_>, message: String) -> PyResult { let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("code", TARGET_ERROR_CODE)?; options.set_item("help", BARE_USAGE)?; - let error: Bound<'_, PyAny> = py - .import(ERRORS_MODULE)? - .getattr("CliUserError")? - .call((message,), Some(&options))?; + let error: Bound<'_, PyAny> = + registry::error(py, ErrorId::CliUser)?.call((message,), Some(&options))?; Ok(PyErr::from_value(error)) } @@ -163,7 +159,7 @@ fn report_filters<'py>( py: Python<'py>, request: &Bound<'py, PyAny>, ) -> PyResult> { - let kind_class: Bound<'_, PyAny> = py.import(TYPES_MODULE)?.getattr("DeclarationKind")?; + let kind_class: Bound<'_, PyAny> = registry::class(py, RecordId::DeclarationKind)?; let mut kinds: Vec> = Vec::new(); for kind in request.getattr("kinds")?.try_iter()? { kinds.push(kind_class.call1((kind?,))?); @@ -183,9 +179,7 @@ fn report_filters<'py>( ("globals", request.getattr("globals")?), ], )?; - py.import(MODELS_MODULE)? - .getattr("ScopeReportFilters")? - .call((), Some(&options)) + registry::class(py, RecordId::ScopeReportFilters)?.call((), Some(&options)) } /// `dataclasses.replace(report, **changes)`. @@ -341,14 +335,13 @@ fn run_scope_command( load_scope_index: Option>, output_stream: Option>, ) -> PyResult { - let native: Bound<'_, PyModule> = py.import(NATIVE_MODULE)?; let project_dir: Bound<'_, PyAny> = match request.getattr("project_dir")? { directory if !directory.is_none() => directory, _ => py.import("pathlib")?.getattr("Path")?.call_method0("cwd")?, }; let loader: Bound<'_, PyAny> = match load_scope_index { Some(loader) => loader, - None => native.getattr("load_or_build_scope_index")?, + None => registry::native(py, NativeAttr::LoadOrBuildScopeIndex)?, }; let options: Bound<'_, PyDict> = keywords( py, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_errors.rs b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_errors.rs index 56c42b1674..ecc9d77def 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_errors.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_errors.rs @@ -1,21 +1,17 @@ //! Report a scope index's error findings as individual compile diagnostics. +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyString, PyTuple}; -const COLLECTOR_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const ERROR_SEVERITY: &str = "error"; /// `report_scope_index_errors`: each error-severity finding of `index`, keyed for the collector. pub(crate) fn report_scope_index_errors(index: &Bound<'_, PyAny>) -> PyResult<()> { let py: Python<'_> = index.py(); - let report = py - .import(COLLECTOR_MODULE)? - .getattr("report_compile_diagnostic")?; - let compiler_diagnostic = py - .import(COMPILE_MODELS_MODULE)? - .getattr("CompilerDiagnostic")?; + let report = registry::native(py, NativeAttr::ReportCompileDiagnostic)?; + let compiler_diagnostic = registry::class(py, RecordId::CompilerDiagnostic)?; let path_class = py.import("pathlib")?.getattr("Path")?; for item in index.getattr("diagnostics")?.try_iter()? { let item: Bound<'_, PyAny> = item?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_index.rs b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_index.rs index 742be7540d..21bd4d6cfc 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_index.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_index.rs @@ -20,6 +20,8 @@ use sqlbuild_scopes::scope_index::models::{ }; use crate::bindings::_helpers::boundary::panics::compiler_error; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::scopes::relationship_scan::{ RelationshipScanSettings, ScenarioRow, TestFileRow, scanned_relationships, }; @@ -32,7 +34,6 @@ use crate::bindings::types::CompilerDetach; /// Why `UnindexableScopeError` is raised; discovery only yields normalized project paths. const UNINDEXABLE_SCOPE_MESSAGE: &str = "Native scope indexing could not place a discovered path or resource kind. Discovery always yields normalized project-relative paths, so this is a SQLBuild bug; please file an issue with the project layout"; -const EXCEPTIONS_MODULE: &str = "sqlbuild._native"; const INSPECT_MODULE: &str = "inspect"; const CODE_TYPE_MODULE: &str = "types"; const PATHLIB_MODULE: &str = "pathlib"; @@ -145,8 +146,8 @@ impl<'py> Walk<'py> { ) -> PyResult<()> { let (path, root) = location; self.resource_identities - .push(self.classes.model("ResourceIdentity")?.call1(( - self.classes.member("ResourceKind", kind.as_str())?, + .push(self.classes.model(RecordId::ResourceIdentity)?.call1(( + self.classes.member(RecordId::ResourceKind, kind.as_str())?, name.as_str(), ))?); self.resource_roots.push((kind, root)); @@ -223,8 +224,9 @@ impl<'py> Walk<'py> { name: &Bound<'py, PyAny>, owner: Option<&Bound<'py, PyAny>>, ) -> PyResult> { - self.classes.model("DeclarationIdentity")?.call1(( - self.classes.member("DeclarationKind", kind.as_str())?, + self.classes.model(RecordId::DeclarationIdentity)?.call1(( + self.classes + .member(RecordId::DeclarationKind, kind.as_str())?, name, owner, )) @@ -246,12 +248,12 @@ impl<'py> Walk<'py> { } fn enum_metadata(&self, declaration: &Bound<'py, PyAny>) -> PyResult> { - let member_class: Bound<'py, PyAny> = self.classes.model("EnumMemberMetadata")?; + let member_class: Bound<'py, PyAny> = self.classes.model(RecordId::EnumMemberMetadata)?; let members: Vec> = items(declaration, "members")? .iter() .map(|member| member_class.call1((member.getattr("name")?,))) .collect::>()?; - self.classes.model("EnumMetadata")?.call1(( + self.classes.model(RecordId::EnumMetadata)?.call1(( PyTuple::new(declaration.py(), members)?, declaration.getattr("scalar_type")?, )) @@ -271,7 +273,7 @@ impl<'py> Walk<'py> { let render_as: Option> = optional_item(declaration, "render_as")? .map(|rendering| rendering.getattr("value")) .transpose()?; - self.classes.model("ConstantMetadata")?.call1(( + self.classes.model(RecordId::ConstantMetadata)?.call1(( declaration .getattr("logical_type")? .getattr("display_name")?, @@ -295,14 +297,15 @@ impl<'py> Walk<'py> { model_name.clone(), (&path, ResourceRoot::Kind), )?; - let owner: Bound<'py, PyAny> = self.classes.model("ResourceIdentity")?.call1(( - self.classes - .member("ResourceKind", ResourceKind::Model.as_str())?, - model_name, - ))?; + let owner: Bound<'py, PyAny> = + self.classes.model(RecordId::ResourceIdentity)?.call1(( + self.classes + .member(RecordId::ResourceKind, ResourceKind::Model.as_str())?, + model_name, + ))?; let private: Bound<'py, PyAny> = self .classes - .member("ScopeKind", ScopeKind::Private.as_str())?; + .member(RecordId::ScopeKind, ScopeKind::Private.as_str())?; for declaration in items(&model_file, "enum_declarations")? { let identity: Bound<'py, PyAny> = self.declaration_identity( DeclarationKind::Enum, @@ -501,7 +504,7 @@ impl<'py> Walk<'py> { Some(file) => file.getattr("scope_kind")?, None => self .classes - .member("ScopeKind", ScopeKind::Global.as_str())?, + .member(RecordId::ScopeKind, ScopeKind::Global.as_str())?, }; let dependencies: Vec> = items(export, "dependencies")?; let placement: Placement = Placement { @@ -539,7 +542,7 @@ impl<'py> Walk<'py> { .try_iter()? .collect::>()?; let raw_source: String = field(loaded, "raw_source")?; - let metadata: Bound<'py, PyAny> = self.classes.model("MacroMetadata")?.call1(( + let metadata: Bound<'py, PyAny> = self.classes.model(RecordId::MacroMetadata)?.call1(( PyTuple::new(py, parameters)?, loaded.getattr("dependencies")?, macro_source_digest(&raw_source), @@ -577,11 +580,12 @@ impl<'py> Walk<'py> { for export in static_macros.try_iter()? { let export: Bound<'py, PyAny> = export?; let key: String = normalized(py, &export.getattr("relative_path")?)?; - let metadata: Bound<'py, PyAny> = self.classes.model("MacroMetadata")?.call1(( - export.getattr("parameters")?, - export.getattr("dependencies")?, - export.getattr("source_digest")?, - ))?; + let metadata: Bound<'py, PyAny> = + self.classes.model(RecordId::MacroMetadata)?.call1(( + export.getattr("parameters")?, + export.getattr("dependencies")?, + export.getattr("source_digest")?, + ))?; let (values, placement) = self.macro_values( macro_files.get(&key), (&export, field(&export, "line")?, metadata), @@ -603,9 +607,7 @@ fn normalized(py: Python<'_>, path: &Bound<'_, PyAny>) -> PyResult { fn unindexable(py: Python<'_>) -> PyResult { Ok(PyErr::from_value( - py.import(EXCEPTIONS_MODULE)? - .getattr("UnindexableScopeError")? - .call1((UNINDEXABLE_SCOPE_MESSAGE,))?, + registry::error(py, ErrorId::UnindexableScope)?.call1((UNINDEXABLE_SCOPE_MESSAGE,))?, )) } @@ -631,15 +633,17 @@ fn resource_root<'py>( Some((_, path)) => (path, "resource", kind), None => { return Err(PyKeyError::new_err( - classes.member("ResourceKind", kind.as_str())?.unbind(), + classes + .member(RecordId::ResourceKind, kind.as_str())? + .unbind(), )); } }, }; - classes.model("OwnershipRoot")?.call1(( + classes.model(RecordId::OwnershipRoot)?.call1(( path, - classes.member("OwnershipRootKind", root_kind)?, - classes.member("ResourceKind", resource_kind.as_str())?, + classes.member(RecordId::OwnershipRootKind, root_kind)?, + classes.member(RecordId::ResourceKind, resource_kind.as_str())?, )) } @@ -653,7 +657,7 @@ fn materialise<'py>(walk: Walk<'py>, native: ScopeIndex) -> PyResult(walk: Walk<'py>, native: ScopeIndex) -> PyResult = classes.model("UsageRecord")?; + let usage_class: Bound<'py, PyAny> = classes.model(RecordId::UsageRecord)?; let dependency_kind: Bound<'py, PyAny> = - classes.member("UsageKind", "declaration_dependency")?; + classes.member(RecordId::UsageKind, "declaration_dependency")?; let mut usages: Vec> = Vec::with_capacity(native.usages.len()); for usage in &native.usages { usages.push(usage_class.call1(( @@ -691,7 +695,7 @@ fn materialise<'py>(walk: Walk<'py>, native: ScopeIndex) -> PyResult = classes.model("ScopeDiagnostic")?; + let diagnostic_class: Bound<'py, PyAny> = classes.model(RecordId::ScopeDiagnostic)?; let mut diagnostics: Vec> = Vec::with_capacity(native.diagnostics.len()); for diagnostic in &native.diagnostics { let keywords: Bound<'py, PyDict> = PyDict::new(py); @@ -714,7 +718,7 @@ fn materialise<'py>(walk: Walk<'py>, native: ScopeIndex) -> PyResult(walk: Walk<'py>, native: ScopeIndex) -> PyResult>()?; let completeness: Bound<'py, PyAny> = { @@ -743,14 +749,14 @@ fn materialise<'py>(walk: Walk<'py>, native: ScopeIndex) -> PyResult], order: &[usize]| -> PyResult> { PyTuple::new(py, order.iter().map(|position| records[*position].clone())) }; - let index: Bound<'py, PyAny> = classes.model("ScopeIndex")?.call1(( + let index: Bound<'py, PyAny> = classes.model(RecordId::ScopeIndex)?.call1(( PyTuple::new(py, roots)?, ordered(&resources, &native.resource_order)?, ordered(&declarations, &native.declaration_order)?, @@ -841,8 +847,8 @@ impl NativeScopeIndex { let mut records: Vec> = Vec::with_capacity(grants.len()); for grant in &grants { let through: Bound<'_, PyAny> = match &grant.through { - GrantThrough::Model(name) => classes.model("ResourceIdentity")?.call1(( - classes.member("ResourceKind", ResourceKind::Model.as_str())?, + GrantThrough::Model(name) => classes.model(RecordId::ResourceIdentity)?.call1(( + classes.member(RecordId::ResourceKind, ResourceKind::Model.as_str())?, name.as_str(), ))?, GrantThrough::Declaration(position) => { @@ -850,16 +856,16 @@ impl NativeScopeIndex { } }; records.push( - classes.model("GrantRecord")?.call1(( - classes.model("ResourceIdentity")?.call1(( - classes.member("ResourceKind", grant.resource.kind.as_str())?, + classes.model(RecordId::GrantRecord)?.call1(( + classes.model(RecordId::ResourceIdentity)?.call1(( + classes.member(RecordId::ResourceKind, grant.resource.kind.as_str())?, grant.resource.name.as_str(), ))?, self.declarations[grant.declaration] .bind(py) .getattr("identity")?, through, - classes.member("GrantKind", grant.kind.as_str())?, + classes.member(RecordId::GrantKind, grant.kind.as_str())?, ))?, ); } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_loading.rs b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_loading.rs index 53daffc840..05a24599f4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_loading.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_loading.rs @@ -9,9 +9,14 @@ use pyo3::prelude::{ use pyo3::types::{PyDict, PyDictMethods, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::discovery::scope_snapshot::discover_scope_snapshot; use crate::bindings::_helpers::frontier::cache_directory::cache_directory; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::scopes::scope_cache::{ project_fingerprint, read_scope_index_cache, write_scope_index_cache, }; @@ -20,17 +25,8 @@ use crate::bindings::_helpers::scopes::scope_tolerant::scope_tolerant_index; use crate::bindings::_helpers::scopes::scope_validation::scope_index_with_compile_usages; use crate::bindings::_helpers::sql_test_glue::constants::SQL_TEST_EXPECTED_MODELS_SCAN_ALGORITHM; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; const SCOPE_CACHE_DIRECTORY_NAME: &str = "declaration-scopes-v2"; const SCOPE_CACHE_FILENAME: &str = "scope-index.json"; -const SQL_ANALYSIS_MODELS_MODULE: &str = "sqlbuild._native"; -const ADAPTER_NAME_MODULE: &str = "sqlbuild._native"; -const COLLECTION_RENDERING_MODULE: &str = "sqlbuild._native"; -const RESOLVE_ADAPTER_MODULE: &str = "sqlbuild.adapter.discovery.main.resolve_adapter"; -const FRONTIER_TYPES_MODULE: &str = "sqlbuild._native"; -const MACRO_INVENTORY_MODULE: &str = "sqlbuild.compiler.compile._helpers.render.user_macros"; /// Where the scope index cache file of a project lives: `(directory, file name)`. fn cache_location(project_dir: &Bound<'_, PyAny>) -> PyResult<(PathBuf, String)> { @@ -55,17 +51,17 @@ fn project_adapter<'py>( let discover_keywords: Bound<'py, PyDict> = PyDict::new(py); discover_keywords.set_item("project_dir", project_dir)?; discover_keywords.set_item("extract_output_column_locations", false)?; - let discovered: Bound<'py, PyAny> = imported(py, NATIVE_MODULE, "discover_project_inputs")? + let discovered: Bound<'py, PyAny> = registry::native(py, NativeAttr::DiscoverProjectInputs)? .call((), Some(&discover_keywords))?; let adapter_name: Bound<'py, PyAny> = call_with( - &imported(py, ADAPTER_NAME_MODULE, "resolve_effective_adapter_name")?, + ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, &[ ("project_config", discovered.getattr("project_config")?), ("local_config", discovered.getattr("local_config")?), ], )?; let adapter: Bound<'py, PyAny> = call_with( - &imported(py, RESOLVE_ADAPTER_MODULE, "resolve_adapter")?, + &host::entry(py, AdapterHost::ResolveAdapter)?, &[("adapter_name", adapter_name)], )?; Ok((discovered, adapter)) @@ -80,18 +76,14 @@ fn compiled_index<'py>( let py: Python<'py> = discovered.py(); let project_config: Bound<'py, PyAny> = discovered.getattr("project_config")?; let collection_rendering: Bound<'py, PyAny> = call_with( - &imported( - py, - COLLECTION_RENDERING_MODULE, - "resolve_effective_collection_rendering", - )?, + ®istry::native(py, NativeAttr::ResolveEffectiveCollectionRendering)?, &[ ("project_config", project_config), ("declaration_override", py.None().into_bound(py)), ], )?; let adapter_context: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "CompileAdapterContext")?, + ®istry::class(py, RecordId::CompileAdapterContext)?, &[ ("value_renderer", adapter.clone()), ("collection_rendering", collection_rendering), @@ -110,16 +102,15 @@ fn compiled_index<'py>( stage_keywords.set_item("defer_model_sql_validation", true)?; stage_keywords.set_item("no_cache", true)?; let python_stage: Bound<'py, PyAny> = py.import("functools")?.getattr("partial")?.call( - (imported(py, NATIVE_MODULE, "build_compile_inputs")?,), + (registry::native(py, NativeAttr::BuildCompileInputs)?,), Some(&stage_keywords), )?; let compile_inputs: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "compile_frontier")?, + ®istry::native(py, NativeAttr::CompileFrontier)?, &[ ( "until", - imported(py, FRONTIER_TYPES_MODULE, "CompilerStage")? - .getattr("COMPILE_PROJECT_INPUTS")?, + registry::class(py, RecordId::CompilerStage)?.getattr("COMPILE_PROJECT_INPUTS")?, ), ("python_stage", python_stage), ], @@ -136,7 +127,7 @@ fn tolerant_index<'py>( let snapshot: Bound<'py, PyAny> = discover_scope_snapshot(project_dir)?; let discovered: Bound<'py, PyAny> = snapshot.getattr("discovered_inputs")?; let inventory: Bound<'py, PyAny> = call_with( - &imported(py, MACRO_INVENTORY_MODULE, "inventory_project_macros")?, + &host::entry(py, UserCodeHost::InventoryProjectMacros)?, &[("macro_files", discovered.getattr("macro_files")?)], )?; let native: Bound<'py, PyAny> = Bound::new( @@ -192,8 +183,7 @@ fn load_or_build_scope_index<'py>( return Ok(cached.into_bound(py)); } } - let mut syntax: Bound<'py, PyAny> = - imported(py, SQL_ANALYSIS_MODELS_MODULE, "SqlLexicalSyntax")?.call0()?; + let mut syntax: Bound<'py, PyAny> = registry::class(py, RecordId::SqlLexicalSyntax)?.call0()?; let compiled: PyResult> = match project_adapter(&resolved) { Ok((discovered, adapter)) => { syntax = adapter.getattr("sql_lexical_syntax")?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_lookup.rs b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_lookup.rs index 2c489b1889..1f7cd1852f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_lookup.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_lookup.rs @@ -17,6 +17,8 @@ use sqlbuild_scopes::scope_index::models::{ }; use crate::bindings::_helpers::boundary::panics::compiler_error; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::scopes::placement::{QualifiedRow, qualified_identity}; use crate::bindings::_helpers::scopes::scope_objects::{ ScopeClasses, read_index, report_records, resource_identity_object, tuple_attribute, @@ -24,7 +26,6 @@ use crate::bindings::_helpers::scopes::scope_objects::{ use crate::bindings::types::CompilerDetach; const TYPES_MODULE: &str = "types"; -const EXCEPTIONS_MODULE: &str = "sqlbuild._native"; /// `(declarations, canonical positions)` matched, else identity and path error messages. pub(crate) type TargetRows = (bool, Vec, Option, Option); @@ -250,7 +251,7 @@ fn visibility_index<'py>( PyTuple::new(py, positions)?, )?; } - classes.model("DeclarationVisibilityIndex")?.call1(( + classes.model(RecordId::DeclarationVisibilityIndex)?.call1(( PyTuple::new(py, identities)?, PyTuple::new(py, &visibility.global)?, mapping_proxy(&private)?, @@ -261,9 +262,7 @@ fn visibility_index<'py>( pub(crate) fn scope_path_error(py: Python<'_>, message: String) -> PyResult { Ok(PyErr::from_value( - py.import(EXCEPTIONS_MODULE)? - .getattr("InvalidScopePathError")? - .call1((message,))?, + registry::error(py, ErrorId::InvalidScopePath)?.call1((message,))?, )) } @@ -285,7 +284,7 @@ pub(crate) fn build_scope_lookup(py: Python<'_>, index: &Bound<'_, PyAny>) -> Py let usages: Bound<'_, PyTuple> = tuple_attribute(index, "usages")?; let grants: Bound<'_, PyTuple> = tuple_attribute(index, "grants")?; let canonical_declarations: Bound<'_, PyTuple> = picked(&declarations, &lookup.declarations)?; - let canonical_index: Bound<'_, PyAny> = classes.model("ScopeIndex")?.call1(( + let canonical_index: Bound<'_, PyAny> = classes.model(RecordId::ScopeIndex)?.call1(( picked(&tuple_attribute(index, "ownership_roots")?, &lookup.roots)?, picked(&resources, &lookup.resources)?, &canonical_declarations, @@ -333,7 +332,7 @@ pub(crate) fn build_scope_lookup(py: Python<'_>, index: &Bound<'_, PyAny>) -> Py by_resource, ] = mappings; Ok(classes - .model("ScopeLookup")? + .model(RecordId::ScopeLookup)? .call1(( canonical_index, by_identity, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_objects.rs b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_objects.rs index ffeafab18c..a2ebbd44cf 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_objects.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_objects.rs @@ -1,6 +1,6 @@ //! Python `ScopeIndex` models read into the native full index, and built back from it. -use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyResult, Python}; +use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::PyTuple; use sqlbuild_scopes::placement::models::{PlacementIndex, PlacementUsage}; use sqlbuild_scopes::scope_cache::models::{ @@ -17,44 +17,40 @@ use sqlbuild_scopes::scope_report::models::{ ProjectionGrant, ProjectionIndex, ProjectionStanding, ProjectionUsage, ReportValues, }; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::scopes::placement::{ IdentityRow, QualifiedRow, RootRow, declaration_identity, declaration_record, ownership_root, qualified_identity, resource_identity, }; use crate::bindings::_helpers::scopes::scope_lookup::grant_kind; -const MODELS_MODULE: &str = "sqlbuild._native"; -const TYPES_MODULE: &str = "sqlbuild._native"; const ENUM_MODULE: &str = "enum"; -/// The scope model and enum modules every built object comes from. +/// The native scope model and enum classes every built object comes from. pub(crate) struct ScopeClasses<'py> { - models: Bound<'py, PyModule>, - types: Bound<'py, PyModule>, + py: Python<'py>, } impl<'py> ScopeClasses<'py> { - /// Import the scope models and enums. + /// The scope classes of `py`. pub(crate) fn new(py: Python<'py>) -> PyResult { - Ok(Self { - models: py.import(MODELS_MODULE)?, - types: py.import(TYPES_MODULE)?, - }) + Ok(Self { py }) } /// One scope model class. - pub(crate) fn model(&self, name: &str) -> PyResult> { - self.models.getattr(name) + pub(crate) fn model(&self, name: RecordId) -> PyResult> { + registry::class(self.py, name) } /// The member of scope enum `name` with `value`; an unknown value raises `ValueError`. - pub(crate) fn member(&self, name: &str, value: &str) -> PyResult> { - self.types.getattr(name)?.call1((value,)) + pub(crate) fn member(&self, name: RecordId, value: &str) -> PyResult> { + registry::class(self.py, name)?.call1((value,)) } /// The interpreter the classes belong to. pub(crate) fn py(&self) -> Python<'py> { - self.models.py() + self.py } } @@ -464,8 +460,8 @@ pub(crate) fn resource_identity_object<'py>( classes: &ScopeClasses<'py>, identity: &ResourceIdentity, ) -> PyResult> { - classes.model("ResourceIdentity")?.call1(( - classes.member("ResourceKind", identity.kind.as_str())?, + classes.model(RecordId::ResourceIdentity)?.call1(( + classes.member(RecordId::ResourceKind, identity.kind.as_str())?, identity.name.as_str(), )) } @@ -480,8 +476,8 @@ pub(crate) fn declaration_identity_object<'py>( .as_ref() .map(|owner| resource_identity_object(classes, owner)) .transpose()?; - classes.model("DeclarationIdentity")?.call1(( - classes.member("DeclarationKind", identity.kind.as_str())?, + classes.model(RecordId::DeclarationIdentity)?.call1(( + classes.member(RecordId::DeclarationKind, identity.kind.as_str())?, identity.name.as_str(), owner, )) @@ -514,11 +510,11 @@ pub(crate) fn root_object<'py>( ) -> PyResult> { let resource_kind: Option> = root .resource_kind - .map(|kind| classes.member("ResourceKind", ResourceKind::as_str(kind))) + .map(|kind| classes.member(RecordId::ResourceKind, ResourceKind::as_str(kind))) .transpose()?; - classes.model("OwnershipRoot")?.call1(( + classes.model(RecordId::OwnershipRoot)?.call1(( root.path.as_str(), - classes.member("OwnershipRootKind", root.kind.as_str())?, + classes.member(RecordId::OwnershipRootKind, root.kind.as_str())?, resource_kind, )) } @@ -527,7 +523,7 @@ fn resource_object<'py>( classes: &ScopeClasses<'py>, record: &ResourceRecord, ) -> PyResult> { - classes.model("ResourceRecord")?.call1(( + classes.model(RecordId::ResourceRecord)?.call1(( resource_identity_object(classes, &record.identity)?, record.path.as_str(), root_object(classes, &record.ownership_root)?, @@ -548,7 +544,7 @@ fn macro_object<'py>( facts: &MacroFacts, ) -> PyResult> { let py: Python<'py> = classes.py(); - classes.model("MacroMetadata")?.call1(( + classes.model(RecordId::MacroMetadata)?.call1(( PyTuple::new(py, &facts.parameters)?, tuple_of(py, &facts.dependencies, |identity| { declaration_identity_object(classes, identity) @@ -558,8 +554,8 @@ fn macro_object<'py>( } fn enum_object<'py>(classes: &ScopeClasses<'py>, facts: &EnumFacts) -> PyResult> { - let member_class: Bound<'py, PyAny> = classes.model("EnumMemberMetadata")?; - classes.model("EnumMetadata")?.call1(( + let member_class: Bound<'py, PyAny> = classes.model(RecordId::EnumMemberMetadata)?; + classes.model(RecordId::EnumMetadata)?.call1(( tuple_of(classes.py(), &facts.members, |name| { member_class.call1((name.as_str(),)) })?, @@ -571,7 +567,7 @@ fn constant_object<'py>( classes: &ScopeClasses<'py>, facts: &ConstantFacts, ) -> PyResult> { - classes.model("ConstantMetadata")?.call1(( + classes.model(RecordId::ConstantMetadata)?.call1(( facts.logical_type.as_str(), facts.collection_kind.as_deref(), facts.item_count, @@ -600,12 +596,12 @@ fn declaration_object<'py>( .transpose()?, ]; let [macro_metadata, enum_metadata, constant_metadata] = values; - classes.model("DeclarationRecord")?.call1(( + classes.model(RecordId::DeclarationRecord)?.call1(( declaration_identity_object(classes, &record.identity)?, record.path.as_str(), record.line, record.column, - classes.member("ScopeKind", record.scope.as_str())?, + classes.member(RecordId::ScopeKind, record.scope.as_str())?, root_object(classes, &record.ownership_root)?, record.owning_path.as_deref(), macro_metadata, @@ -618,10 +614,10 @@ fn usage_object<'py>( classes: &ScopeClasses<'py>, item: &CachedUsage, ) -> PyResult> { - classes.model("UsageRecord")?.call1(( + classes.model(RecordId::UsageRecord)?.call1(( qualified_object(classes, &item.consumer)?, declaration_identity_object(classes, &item.declaration)?, - classes.member("UsageKind", &item.kind)?, + classes.member(RecordId::UsageKind, &item.kind)?, optional_qualified_object(classes, item.through.as_ref())?, item.enum_member.as_deref(), )) @@ -631,11 +627,11 @@ fn grant_object<'py>( classes: &ScopeClasses<'py>, item: &ReportGrant, ) -> PyResult> { - classes.model("GrantRecord")?.call1(( + classes.model(RecordId::GrantRecord)?.call1(( resource_identity_object(classes, &item.resource)?, declaration_identity_object(classes, &item.declaration)?, qualified_object(classes, &item.through)?, - classes.member("GrantKind", item.kind.as_str())?, + classes.member(RecordId::GrantKind, item.kind.as_str())?, )) } @@ -643,10 +639,10 @@ fn visibility_object<'py>( classes: &ScopeClasses<'py>, item: &CachedVisibility, ) -> PyResult> { - classes.model("VisibilityRecord")?.call1(( + classes.model(RecordId::VisibilityRecord)?.call1(( resource_identity_object(classes, &item.resource)?, declaration_identity_object(classes, &item.declaration)?, - classes.member("VisibilityReason", &item.reason)?, + classes.member(RecordId::VisibilityReason, &item.reason)?, optional_qualified_object(classes, item.through.as_ref())?, )) } @@ -655,10 +651,10 @@ fn inaccessible_object<'py>( classes: &ScopeClasses<'py>, item: &CachedInaccessible, ) -> PyResult> { - classes.model("InaccessibleRecord")?.call1(( + classes.model(RecordId::InaccessibleRecord)?.call1(( resource_identity_object(classes, &item.resource)?, declaration_identity_object(classes, &item.declaration)?, - classes.member("InaccessibleReason", &item.reason)?, + classes.member(RecordId::InaccessibleReason, &item.reason)?, )) } @@ -667,10 +663,10 @@ pub(crate) fn diagnostic_object<'py>( classes: &ScopeClasses<'py>, item: &CachedDiagnostic, ) -> PyResult> { - classes.model("ScopeDiagnostic")?.call1(( - classes.member("ScopeDiagnosticCode", &item.code)?, + classes.model(RecordId::ScopeDiagnostic)?.call1(( + classes.member(RecordId::ScopeDiagnosticCode, &item.code)?, item.message.as_str(), - classes.member("ScopeDiagnosticSeverity", &item.severity)?, + classes.member(RecordId::ScopeDiagnosticSeverity, &item.severity)?, item.path.as_deref(), item.line, item.column, @@ -691,7 +687,7 @@ pub(crate) fn completeness_object<'py>( done: [bool; 6], ) -> PyResult> { classes - .model("ScopeCompleteness")? + .model(RecordId::ScopeCompleteness)? .call1((done[0], done[1], done[2], done[3], done[4], done[5])) } @@ -701,7 +697,7 @@ pub(crate) fn index_object<'py>( index: &CachedIndex, ) -> PyResult> { let py: Python<'py> = classes.py(); - classes.model("ScopeIndex")?.call1(( + classes.model(RecordId::ScopeIndex)?.call1(( tuple_of(py, &index.roots, |root| root_object(classes, root))?, tuple_of(py, &index.resources, |record| { resource_object(classes, record) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_report_objects.rs b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_report_objects.rs index bbfbf1599f..01b529f61c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_report_objects.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_report_objects.rs @@ -14,6 +14,7 @@ use sqlbuild_scopes::scope_report::models::{ ScopeFolder, ScopeReport, ScopeResult, ScopeSection, SourceLocation, }; +use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::scopes::scope_objects::{ ScopeClasses, diagnostic_facts, field, optional_item, read_declaration_identity, read_diagnostic, read_qualified, @@ -35,8 +36,8 @@ pub(crate) fn read_target( classes: &ScopeClasses<'_>, target: &Bound<'_, PyAny>, ) -> PyResult { - if target.is_instance(&classes.model("ResourceIdentity")?)? - || target.is_instance(&classes.model("DeclarationIdentity")?)? + if target.is_instance(&classes.model(RecordId::ResourceIdentity)?)? + || target.is_instance(&classes.model(RecordId::DeclarationIdentity)?)? { return Ok(QueryTarget::Identity(read_qualified(target)?)); } @@ -65,7 +66,7 @@ pub(crate) fn read_declaration_target( classes: &ScopeClasses<'_>, declaration: &Bound<'_, PyAny>, ) -> PyResult { - if declaration.is_instance(&classes.model("DeclarationIdentity")?)? { + if declaration.is_instance(&classes.model(RecordId::DeclarationIdentity)?)? { return Ok(DeclarationTarget::Identity(read_declaration_identity( declaration, )?)); @@ -215,7 +216,7 @@ fn declaration_object<'py>( .visibility .map(|provenance| { classes - .model("VisibilityProvenance")? + .model(RecordId::VisibilityProvenance)? .call1((provenance.reason, provenance.through)) }) .transpose()?; @@ -224,7 +225,7 @@ fn declaration_object<'py>( report.kind.into_bound_py_any(py)?, report.name.into_bound_py_any(py)?, report.owner.into_bound_py_any(py)?, - classes.model("ScopeSourceLocation")?.call1(( + classes.model(RecordId::ScopeSourceLocation)?.call1(( report.definition.path, report.definition.line, report.definition.column, @@ -247,7 +248,7 @@ fn declaration_object<'py>( ]; let arguments: Vec> = head.into_iter().chain(standing).chain(links).collect(); classes - .model("DeclarationReport")? + .model(RecordId::DeclarationReport)? .call1(PyTuple::new(py, arguments)?) } @@ -264,7 +265,7 @@ fn section_object<'py>( classes: &ScopeClasses<'py>, section: ScopeSection, ) -> PyResult> { - classes.model("ScopeSection")?.call1(( + classes.model(RecordId::ScopeSection)?.call1(( section.name, section.total, section.returned, @@ -290,9 +291,9 @@ pub(crate) fn diagnostics_object<'py>( ReportDiagnostic::Raised(facts) => { let keywords: Bound<'py, PyDict> = PyDict::new(py); keywords.set_item("path", facts.path)?; - classes.model("ScopeDiagnostic")?.call( + classes.model(RecordId::ScopeDiagnostic)?.call( ( - classes.member("ScopeDiagnosticCode", &facts.code)?, + classes.member(RecordId::ScopeDiagnosticCode, &facts.code)?, facts.message, ), Some(&keywords), @@ -311,7 +312,7 @@ pub(crate) fn explanation_object<'py>( .map(|report| declaration_object(classes, report)) .transpose()?; classes - .model("DeclarationExplanation")? + .model(RecordId::DeclarationExplanation)? .call1((declaration, explanation.complete)) } @@ -320,7 +321,7 @@ pub(crate) fn move_object<'py>( classes: &ScopeClasses<'py>, preview: MovePreview, ) -> PyResult> { - classes.model("MovePreview")?.call1(( + classes.model(RecordId::MovePreview)?.call1(( preview.resource, preview.destination, preview.new_ownership_root, @@ -343,7 +344,7 @@ pub(crate) fn report_object<'py>( let (filters, index_diagnostics) = context; let py: Python<'py> = classes.py(); let resource: ResourceReport = report.resource; - let chain_class: Bound<'py, PyAny> = classes.model("ScopeChainEntry")?; + let chain_class: Bound<'py, PyAny> = classes.model(RecordId::ScopeChainEntry)?; let explanation: Option> = report .explanation .map(|explanation| explanation_object(classes, explanation)) @@ -352,8 +353,8 @@ pub(crate) fn report_object<'py>( .move_preview .map(|preview| move_object(classes, preview)) .transpose()?; - classes.model("ScopeReport")?.call1(( - classes.model("ScopeResourceReport")?.call1(( + classes.model(RecordId::ScopeReport)?.call1(( + classes.model(RecordId::ScopeResourceReport)?.call1(( resource.target, resource.identity, resource.path, @@ -386,8 +387,8 @@ pub(crate) fn browse_object<'py>( index_diagnostics: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = classes.py(); - let folder_class: Bound<'py, PyAny> = classes.model("ScopeFolder")?; - classes.model("ScopeBrowseResult")?.call1(( + let folder_class: Bound<'py, PyAny> = classes.model(RecordId::ScopeFolder)?; + classes.model(RecordId::ScopeBrowseResult)?.call1(( result.folder, tuple_of(py, result.folders, |folder| { folder_class.call1(( @@ -410,7 +411,7 @@ pub(crate) fn list_object<'py>( result: ListResult, index_diagnostics: &Bound<'py, PyAny>, ) -> PyResult> { - classes.model("ScopeListResult")?.call1(( + classes.model(RecordId::ScopeListResult)?.call1(( result.folder, declarations_object(classes, result.declarations)?, section_object(classes, result.section)?, @@ -588,10 +589,10 @@ pub(crate) fn read_result( classes: &ScopeClasses<'_>, result: &Bound<'_, PyAny>, ) -> PyResult { - if result.is_instance(&classes.model("ScopeReport")?)? { + if result.is_instance(&classes.model(RecordId::ScopeReport)?)? { return Ok(ScopeResult::Report(Box::new(read_report(result)?))); } - if result.is_instance(&classes.model("ScopeBrowseResult")?)? { + if result.is_instance(&classes.model(RecordId::ScopeBrowseResult)?)? { return Ok(ScopeResult::Browse(BrowseResult { folder: field(result, "folder")?, folders: result diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_reports.rs b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_reports.rs index 75b24cbe18..1bc4610d7b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_reports.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_reports.rs @@ -21,6 +21,8 @@ use sqlbuild_scopes::scope_report::models::{ }; use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::scopes::scope_lookup::{ NativeScopeLookup, identity_message, path_message, }; @@ -58,7 +60,6 @@ pub(crate) type ListArguments<'py> = ( bool, ); -const EXCEPTIONS_MODULE: &str = "sqlbuild._native"; const CURRENT_FOLDER: &str = "."; /// One lookup's native lookup and every declaration's values in input order. @@ -105,11 +106,9 @@ fn lookup_reports(lookup: &Bound<'_, PyAny>) -> PyResult { }) } -fn scope_error(py: Python<'_>, class: &str, message: String) -> PyResult { +fn scope_error(py: Python<'_>, class: ErrorId, message: String) -> PyResult { Ok(PyErr::from_value( - py.import(EXCEPTIONS_MODULE)? - .getattr(class)? - .call1((message,))?, + registry::error(py, class)?.call1((message,))?, )) } @@ -117,11 +116,11 @@ fn scope_error(py: Python<'_>, class: &str, message: String) -> PyResult fn failure_error(py: Python<'_>, failure: &ReportFailure) -> PyResult { match failure { ReportFailure::Path(rejected) => { - scope_error(py, "InvalidScopePathError", path_message(rejected)) + scope_error(py, ErrorId::InvalidScopePath, path_message(rejected)) } ReportFailure::Identity(failure, text) => scope_error( py, - "InvalidQualifiedIdentityError", + ErrorId::InvalidQualifiedIdentity, identity_message(failure, text), ), ReportFailure::Serialization(message) => Ok(PyValueError::new_err(message.clone())), @@ -153,7 +152,7 @@ pub(crate) fn scope_report( let classes: ScopeClasses<'_> = ScopeClasses::new(py)?; let selected: Bound<'_, PyAny> = match filters.filter(|filters| !filters.is_none()) { Some(filters) => filters, - None => classes.model("ScopeReportFilters")?.call0()?, + None => classes.model(RecordId::ScopeReportFilters)?.call0()?, }; let reports: LookupReports = lookup_reports(lookup)?; let request: ReportRequest = ReportRequest { @@ -304,7 +303,7 @@ pub(crate) fn serialize_scope_report( match serialized(&classes, report) { Err(error) if error.is_instance_of::(py) => { let message: String = error.value(py).str()?.extract()?; - let raised: PyErr = scope_error(py, "ScopeError", message)?; + let raised: PyErr = scope_error(py, ErrorId::Scope, message)?; raised.set_cause(py, Some(error)); Err(raised) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_tolerant.rs b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_tolerant.rs index e0aeede773..09e9c5c0d7 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_tolerant.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_tolerant.rs @@ -8,6 +8,7 @@ use sqlbuild_scopes::scope_index::models::{ OwnershipRoot, OwnershipRootKind, ResourceIdentity, ResourceKind, ResourceRecord, }; +use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::scopes::scope_objects::{ ScopeClasses, enum_text, field, optional_item, read_resource, root_object, tuple_attribute, }; @@ -47,9 +48,9 @@ impl<'py> Diagnostics<'py> { let keywords: Bound<'py, PyDict> = PyDict::new(self.classes.py()); keywords.set_item("path", path.as_deref())?; self.objects - .push(self.classes.model("ScopeDiagnostic")?.call( + .push(self.classes.model(RecordId::ScopeDiagnostic)?.call( ( - self.classes.member("ScopeDiagnosticCode", code)?, + self.classes.member(RecordId::ScopeDiagnosticCode, code)?, message.as_str(), ), Some(&keywords), @@ -112,9 +113,9 @@ fn resource_object<'py>( classes: &ScopeClasses<'py>, record: &ResourceRecord, ) -> PyResult> { - classes.model("ResourceRecord")?.call1(( - classes.model("ResourceIdentity")?.call1(( - classes.member("ResourceKind", record.identity.kind.as_str())?, + classes.model(RecordId::ResourceRecord)?.call1(( + classes.model(RecordId::ResourceIdentity)?.call1(( + classes.member(RecordId::ResourceKind, record.identity.kind.as_str())?, record.identity.name.as_str(), ))?, record.path.as_str(), @@ -212,7 +213,7 @@ pub(crate) fn scope_tolerant_index( declaration_faults.is_empty() && config_faults.is_empty() && macro_faults.is_empty(); let relationships: bool = snapshot_relationship_faults.is_empty() && relationship_faults.is_empty(); - let completeness: Bound<'_, PyAny> = classes.model("ScopeCompleteness")?.call1(( + let completeness: Bound<'_, PyAny> = classes.model(RecordId::ScopeCompleteness)?.call1(( false, static_visibility, false, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_validation.rs b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_validation.rs index 292be2f619..905be51f19 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_validation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_validation.rs @@ -14,6 +14,8 @@ use sqlbuild_scopes::placement::models::{ use sqlbuild_scopes::scope_index::models::ResourceIdentity; use crate::bindings::_helpers::boundary::panics::compiler_error; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::scopes::compile_usages::{ MergeRows, SourceRow, UsageRow, merged_usage_rows, model_schema_scope_usages, }; @@ -25,8 +27,6 @@ use crate::bindings::_helpers::scopes::scope_objects::{ use crate::bindings::types::CompilerDetach; const DATACLASSES_MODULE: &str = "dataclasses"; -const SCOPE_TYPES_MODULE: &str = "sqlbuild._native"; -const SCOPE_ERRORS_MODULE: &str = "sqlbuild._native"; const RUNTIME_USAGE: usize = 2; /// Compile input groups whose items carry declaration usages, after the model inputs. const COMPILE_USAGE_GROUPS: [&str; 5] = [ @@ -219,7 +219,7 @@ fn placement_validated_index<'py>( } let classes: ScopeClasses<'py> = ScopeClasses::new(py)?; let severity: Bound<'py, PyAny> = classes.member( - "ScopeDiagnosticSeverity", + RecordId::ScopeDiagnosticSeverity, if enforce_placement { "error" } else { @@ -231,7 +231,7 @@ fn placement_validated_index<'py>( .compiler_detach(|| Ok(placement_diagnostics(&placement))) .map_err(compiler_error)?; let records: Bound<'py, PyTuple> = tuple_attribute(index, "declarations")?; - let diagnostic_class: Bound<'py, PyAny> = classes.model("ScopeDiagnostic")?; + let diagnostic_class: Bound<'py, PyAny> = classes.model(RecordId::ScopeDiagnostic)?; for item in found { let record: Bound<'py, PyAny> = records.get_item(item.declaration)?; let native = &placement.declarations[item.declaration]; @@ -243,7 +243,7 @@ fn placement_validated_index<'py>( message: item.message.clone(), }); kept.push(diagnostic_class.call1(( - classes.member("ScopeDiagnosticCode", item.code.as_str())?, + classes.member(RecordId::ScopeDiagnosticCode, item.code.as_str())?, item.message, &severity, record.getattr("path")?, @@ -315,7 +315,10 @@ pub(crate) fn scope_relocated_declarations( &records.get_item(item.declaration)?, &[ ("path", item.path.into_bound_py_any(py)?), - ("scope", classes.member("ScopeKind", item.scope.as_str())?), + ( + "scope", + classes.member(RecordId::ScopeKind, item.scope.as_str())?, + ), ( "ownership_root", root_object(&classes, &item.ownership_root)?, @@ -377,10 +380,8 @@ pub(crate) fn scope_index_with_compile_usages<'py>( #[pyo3(signature = (*, index))] pub(crate) fn validate_scope_index(index: &Bound<'_, PyAny>) -> Result<(), PyErr> { let py: Python<'_> = index.py(); - let error: Bound<'_, PyAny> = py - .import(SCOPE_TYPES_MODULE)? - .getattr("ScopeDiagnosticSeverity")? - .getattr("ERROR")?; + let error: Bound<'_, PyAny> = + registry::class(py, RecordId::ScopeDiagnosticSeverity)?.getattr("ERROR")?; let mut errors: Vec> = Vec::new(); for item in index.getattr("diagnostics")?.try_iter()? { let item: Bound<'_, PyAny> = item?; @@ -400,9 +401,7 @@ pub(crate) fn validate_scope_index(index: &Bound<'_, PyAny>) -> Result<(), PyErr let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("diagnostics", PyTuple::new(py, errors)?)?; Err(PyErr::from_value( - py.import(SCOPE_ERRORS_MODULE)? - .getattr("ScopeValidationError")? - .call((message,), Some(&options))?, + registry::error(py, ErrorId::ScopeValidation)?.call((message,), Some(&options))?, )) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_visibility.rs b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_visibility.rs index 3d77e5e4dc..6ef152b40c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_visibility.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_visibility.rs @@ -10,6 +10,8 @@ use sqlbuild_scopes::scope_index::models::{ OwnershipRoot, OwnershipRootKind, RejectedPath, ResourceIdentity, ResourceKind, ResourceRecord, }; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::scopes::scope_lookup::{ NativeScopeLookup, ResolvedRows, TargetRows, path_message, scope_path_error, }; @@ -19,7 +21,6 @@ use crate::bindings::_helpers::scopes::scope_objects::{ type PyObject = Py; -const EXCEPTIONS_MODULE: &str = "sqlbuild._native"; const CURRENT_PATH: &str = "."; fn native_lookup(lookup: &Bound<'_, PyAny>) -> PyResult> { @@ -33,8 +34,8 @@ fn target_query<'py>( target: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = classes.py(); - let identity: bool = target.is_instance(&classes.model("ResourceIdentity")?)? - || target.is_instance(&classes.model("DeclarationIdentity")?)?; + let identity: bool = target.is_instance(&classes.model(RecordId::ResourceIdentity)?)? + || target.is_instance(&classes.model(RecordId::DeclarationIdentity)?)?; let raw: String = if identity { String::new() } else { @@ -49,9 +50,7 @@ fn target_query<'py>( native_lookup(lookup)?.get().target(row, &raw)?; if let Some(message) = identity_error { return Err(PyErr::from_value( - py.import(EXCEPTIONS_MODULE)? - .getattr("InvalidQualifiedIdentityError")? - .call1((message,))?, + registry::error(py, ErrorId::InvalidQualifiedIdentity)?.call1((message,))?, )); } if let Some(message) = path_error { @@ -86,7 +85,7 @@ fn target_query<'py>( matched, )?; classes - .model("ScopeTargetQuery")? + .model(RecordId::ScopeTargetQuery)? .call((value,), Some(&keywords)) } @@ -149,8 +148,8 @@ fn resolved_visibility( tuple_attribute(&lookup.getattr("visibility_index")?, "identities")?; let declarations: Bound<'_, PyTuple> = tuple_attribute(&index, "declarations")?; let grants: Bound<'_, PyTuple> = tuple_attribute(&index, "grants")?; - let visible_class: Bound<'_, PyAny> = classes.model("VisibilityRecord")?; - let inaccessible_class: Bound<'_, PyAny> = classes.model("InaccessibleRecord")?; + let visible_class: Bound<'_, PyAny> = classes.model(RecordId::VisibilityRecord)?; + let inaccessible_class: Bound<'_, PyAny> = classes.model(RecordId::InaccessibleRecord)?; let mut visible: Vec> = Vec::new(); let mut inaccessible: Vec> = Vec::new(); for resource in tuple_attribute(&query, "matches")?.iter() { @@ -164,7 +163,7 @@ fn resolved_visibility( visible.push(visible_class.call1(( &owner, identities.get_item(position)?, - classes.member("VisibilityReason", reason)?, + classes.member(RecordId::VisibilityReason, reason)?, through, ))?); } @@ -175,17 +174,17 @@ fn resolved_visibility( inaccessible_class.call1(( &owner, identity, - classes.member("InaccessibleReason", reason)?, + classes.member(RecordId::InaccessibleReason, reason)?, ))? } else { identity }); } } - let result: &str = if with_reasons { - "VisibilityResolution" + let result: RecordId = if with_reasons { + RecordId::VisibilityResolution } else { - "DeclarationVisibility" + RecordId::DeclarationVisibility }; Ok(classes .model(result)? diff --git a/crates/sqlbuild-python/src/bindings/_helpers/seed_command/command.rs b/crates/sqlbuild-python/src/bindings/_helpers/seed_command/command.rs index 2e907c9cca..4b5ecca39c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/seed_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/seed_command/command.rs @@ -16,17 +16,12 @@ use sqlbuild_core::seed_command::models::SeedRow; use crate::bindings::_helpers::command_runtime::invocation::resolve_command_invocation; use crate::bindings::_helpers::compile_driver::plan_result::connection_with_hooks; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const EXTERNAL_REFS_MODULE: &str = "sqlbuild._native"; -const STATIC_COMMAND_MODULE: &str = "sqlbuild._native"; -const SEED_PLAN_MODULE: &str = "sqlbuild._native"; -const SEED_STATE_MODULE: &str = "sqlbuild._native"; -const RUNTIME_CONTRACT_MODELS: &str = "sqlbuild.runtime.contracts.models"; -const EXECUTOR_RUN_MODULE: &str = "sqlbuild.executor.pipeline.main.run"; const SUCCESS_STATUS: &str = "success"; const FAILED_STATUS: &str = "failed"; const EXECUTION_COMMAND: &str = "sqb seed"; @@ -181,7 +176,7 @@ fn compile_seeds<'py>( let connection_config: Bound<'py, PyAny> = invocation.getattr("connection_config")?; let discovered: Bound<'py, PyAny> = invocation.getattr("discovered_inputs")?; let connection_progress: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "ConnectionProgressReporter")?, + ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, &[ ("adapter_name", invocation.getattr("adapter_name")?), ("stream", stream.clone()), @@ -191,18 +186,14 @@ fn compile_seeds<'py>( stream.call_method1("write", ("\n",))?; stream.call_method0("flush")?; let resolver: Bound<'py, PyAny> = call_with( - &imported( - py, - EXTERNAL_REFS_MODULE, - "resolve_external_sql_reference_resolver", - )?, + ®istry::native(py, NativeAttr::ResolveExternalSqlReferenceResolver)?, &[ ("project_dir", invocation.getattr("effective_project_dir")?), ("discovered_inputs", discovered.clone()), ], )?; let options: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "CompilePipelineOptions")?, + ®istry::class(py, RecordId::CompilePipelineOptions)?, &[ ("selected_target", request.getattr("selected_target")?), ("select", request.getattr("select")?), @@ -213,7 +204,7 @@ fn compile_seeds<'py>( ], )?; let context: Bound<'py, PyAny> = call_with( - &imported(py, STATIC_COMMAND_MODULE, "compile_static_command_context")?, + ®istry::native(py, NativeAttr::CompileStaticCommandContext)?, &[ ("discovered_inputs", discovered), ("adapter", adapter.clone()), @@ -235,7 +226,7 @@ fn compile_seeds<'py>( } else { let relay: Bound<'py, SeedConnectionRelay> = connection_relay(&connection_progress)?; let hooks: Bound<'py, PyAny> = call_with( - &imported(py, RUNTIME_CONTRACT_MODELS, "ConnectionHooks")?, + &host::entry(py, ExecutorHost::ConnectionHooks)?, &[ ( "on_connection_start", @@ -251,7 +242,7 @@ fn compile_seeds<'py>( let connection: Bound<'py, PyAny> = connection_with_hooks(&adapter, &connection_config, &hooks)?; let read: PyResult> = call_with( - &imported(py, SEED_STATE_MODULE, "read_selected_seed_fingerprints")?, + ®istry::native(py, NativeAttr::ReadSelectedSeedFingerprints)?, &[ ("adapter", adapter.clone()), ("connection", connection.clone()), @@ -264,7 +255,7 @@ fn compile_seeds<'py>( read }; let plan_output: Bound<'py, PyAny> = call_with( - &imported(py, SEED_PLAN_MODULE, "build_seed_command_plan")?, + ®istry::native(py, NativeAttr::BuildSeedCommandPlan)?, &[ ("project", project), ("scope", scope), @@ -273,7 +264,7 @@ fn compile_seeds<'py>( ], )?; call_with( - &imported(py, NATIVE_MODULE, "build_static_pipeline_result")?, + ®istry::native(py, NativeAttr::BuildStaticPipelineResult)?, &[("context", context), ("plan_output", plan_output)], ) } @@ -306,7 +297,7 @@ fn write_header( header.set_item("target", py.None())?; header.set_item("concurrency", concurrency)?; header.set_item("use_color", use_color)?; - imported(py, NATIVE_MODULE, "write_execution_header")?.call((), Some(&header))?; + registry::native(py, NativeAttr::WriteExecutionHeader)?.call((), Some(&header))?; Ok(()) } @@ -328,9 +319,9 @@ fn load_seeds<'py>( .map(|(index, name)| (name.clone(), index + 1)) .collect(); let event_writer: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "ExecutionEventWriter")?.call0()?; + registry::native(py, NativeAttr::ExecutionEventWriter)?.call0()?; let projector: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "current_native_progress_projector")?.call0()?; + registry::native(py, NativeAttr::CurrentNativeProgressProjector)?.call0()?; let projector: Option> = if projector.is_none() { None } else { @@ -360,7 +351,7 @@ fn load_seeds<'py>( }, )?; let connection_progress: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "ConnectionProgressReporter")?, + ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, &[ ("adapter_name", invocation.getattr("adapter_name")?), ("stream", stream), @@ -374,7 +365,7 @@ fn load_seeds<'py>( let relay: Bound<'py, SeedConnectionRelay> = connection_relay(&connection_progress)?; let loaded: PyResult>> = (|| { call_with( - &imported(py, EXECUTOR_RUN_MODULE, "run_seed_pipeline")?, + &host::entry(py, ExecutorHost::RunSeedPipeline)?, &[ ("plan", plan), ( @@ -416,7 +407,7 @@ fn load_seeds<'py>( #[pyo3(signature = (*, request))] fn run_seed_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { let invocation: Bound<'_, PyAny> = - resolve_command_invocation(request, &imported(py, NATIVE_MODULE, "SeedInvocation")?)?; + resolve_command_invocation(request, ®istry::class(py, RecordId::SeedInvocation)?)?; let pipeline: Bound<'_, PyAny> = compile_seeds(request, &invocation)?; let requested: Bound<'_, PyAny> = request.getattr("concurrency")?; let concurrency: i64 = if requested.is_none() { @@ -456,14 +447,14 @@ fn run_seed_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult )?; stream.call_method0("flush")?; let payload: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "format_seed_execution_json")?, + ®istry::native(py, NativeAttr::FormatSeedExecutionJson)?, &[ ("results", PyTuple::new(py, &results)?.into_any()), ("plan", pipeline.getattr("plan_output")?), ], )?; call_with( - &imported(py, NATIVE_MODULE, "write_execution_json_output")?, + ®istry::native(py, NativeAttr::WriteExecutionJsonOutput)?, &[ ("payload", payload), ("json_output", request.getattr("json_output")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/inputs.rs b/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/inputs.rs index 0c06652ffe..762df3d046 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/inputs.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/inputs.rs @@ -7,13 +7,12 @@ use crate::bindings::_helpers::analysis::binding_catalog::ignores_quoted_case; use crate::bindings::_helpers::analysis::schema_request::{ SchemaRequestParts, schema_validation_request, }; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::semantic_checks::resource_sql::resource_sql_diagnostics_of; use crate::bindings::_helpers::semantic_shapes::declarations::{declared_types, function_names}; use crate::bindings::models::ProjectCatalog; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ANALYSIS_MODELS_MODULE: &str = "sqlbuild._native"; - /// The column names a metadata config value lists: a name, or the names of a list or tuple. pub(crate) fn column_names(value: &Bound<'_, PyAny>) -> PyResult> { if value.is_instance_of::() { @@ -57,9 +56,7 @@ pub(crate) fn sql_test_columns<'py>( if bodies.is_empty() { return Ok(columns); } - let query_class: Bound<'py, PyAny> = py - .import(ANALYSIS_MODELS_MODULE)? - .getattr("NativeColumnQuery")?; + let query_class: Bound<'py, PyAny> = registry::class(py, RecordId::NativeColumnQuery)?; let mut queries: Vec> = Vec::with_capacity(bodies.len()); for sql in bodies.keys() { let arguments: Bound<'py, PyDict> = PyDict::new(py); @@ -71,10 +68,8 @@ pub(crate) fn sql_test_columns<'py>( let arguments: Bound<'py, PyDict> = PyDict::new(py); arguments.set_item("queries", PyTuple::new(py, queries)?)?; arguments.set_item("profile", profile)?; - let analyses: Bound<'py, PyAny> = py - .import(NATIVE_MODULE)? - .getattr("infer_native_query_columns")? - .call((), Some(&arguments))?; + let analyses: Bound<'py, PyAny> = + registry::native(py, NativeAttr::InferNativeQueryColumns)?.call((), Some(&arguments))?; for (sql, analysis) in bodies.keys().iter().zip(analyses.try_iter()?) { let analysis: Bound<'py, PyAny> = analysis?; let mut names: Vec> = Vec::new(); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/resource_sql.rs b/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/resource_sql.rs index 974a648c5e..2b743d310b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/resource_sql.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/resource_sql.rs @@ -14,12 +14,11 @@ use sqlbuild_analysis::semantic_checks::models::{ }; use crate::bindings::_helpers::boundary::panics::compiler_error; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::models::ProjectCatalog; use crate::bindings::types::CompilerDetach; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; -const CONTRACT_MODELS_MODULE: &str = "sqlbuild._native"; const SQL_ANALYSIS_CONFIG_KEY: &str = "sql_analysis"; const MODEL_HOOK_KEYS: [&str; 2] = ["pre_hooks", "post_hooks"]; const BUILT_IN_AUDIT_ROOT: &str = ""; @@ -53,12 +52,8 @@ pub(crate) fn resource_sql_diagnostics_of<'py>( Err(failure) => return Err(failure_error(py, failure)?), }; let classes = DiagnosticClasses { - compiler_diagnostic: py - .import(COMPILE_MODELS_MODULE)? - .getattr("CompilerDiagnostic")?, - source_location: py - .import(CONTRACT_MODELS_MODULE)? - .getattr("SourceLocation")?, + compiler_diagnostic: registry::class(py, RecordId::CompilerDiagnostic)?, + source_location: registry::class(py, RecordId::SourceLocation)?, path: py.import("pathlib")?.getattr("Path")?, }; let built: Vec> = diagnostics @@ -74,9 +69,7 @@ fn request( profile: &Bound<'_, PyAny>, ) -> PyResult { let py: Python<'_> = project.py(); - let reference_class: Bound<'_, PyAny> = py - .import(COMPILE_MODELS_MODULE)? - .getattr("CompileSqlReference")?; + let reference_class: Bound<'_, PyAny> = registry::class(py, RecordId::CompileSqlReference)?; let mut audits: Vec = Vec::new(); for audit in project.getattr("audits")?.try_iter()? { audits.push(audit_input(&audit?, &reference_class)?); @@ -331,11 +324,9 @@ fn python_diagnostic<'py>( fn failure_error(py: Python<'_>, failure: ResourceSqlFailure) -> PyResult { Ok(match failure { - ResourceSqlFailure::CursorIntrinsic(message) => PyErr::from_value( - py.import(COMPILE_ERRORS_MODULE)? - .getattr("CompileInputError")? - .call1((message,))?, - ), + ResourceSqlFailure::CursorIntrinsic(message) => { + PyErr::from_value(registry::error(py, ErrorId::CompileInput)?.call1((message,))?) + } ResourceSqlFailure::Normalization(message) | ResourceSqlFailure::Internal(message) => { compiler_error(message) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/stage_objects.rs b/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/stage_objects.rs index 7c2a2137b4..183d6014c9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/stage_objects.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/stage_objects.rs @@ -1,22 +1,17 @@ //! The Python models, enums and compile helpers the semantic completion stage reads and builds. -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyString}; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const UNNEEDED_SQL_ANALYSIS_OPT_OUT_CODE: &str = "P009"; const SQL_ANALYSIS_CONFIG_KEY: &str = "sql_analysis"; -const CONTRACT_MODELS_MODULE: &str = "sqlbuild._native"; -const PROJECT_WITH_MODULE: &str = "sqlbuild._native"; -const TYPE_RECOVERY_MODULE: &str = "sqlbuild._native"; -const BINDING_DIAGNOSTICS_MODULE: &str = "sqlbuild._native"; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const NORMALIZE_TYPE_MODULE: &str = "sqlbuild.adapter.type_system.main.normalize_type"; const DATACLASSES_MODULE: &str = "dataclasses"; -const REFERENCE_TYPES_MODULE: &str = "sqlbuild._native"; /// Imported once per stage run. pub(crate) struct StageClasses<'py> { @@ -43,20 +38,16 @@ pub(crate) struct StageClasses<'py> { impl<'py> StageClasses<'py> { pub(crate) fn new(py: Python<'py>) -> PyResult { - let models = py.import(COMPILE_MODELS_MODULE)?; let types = py.import(COMPILE_TYPES_MODULE)?; - let severity = shape_class(types.as_any(), "DiagnosticSeverity")?; + let severity = registry::class(types.py(), RecordId::DiagnosticSeverity)?; let resource = types.getattr("CompiledResourceType")?; - let kinds = py - .import(REFERENCE_TYPES_MODULE)? - .getattr("SqlReferenceKind")?; + let kinds = registry::class(py, RecordId::SqlReferenceKind)?; Ok(Self { - diagnostic: models.getattr("CompilerDiagnostic")?, - location: py - .import(CONTRACT_MODELS_MODULE)? - .getattr("SourceLocation")?, - model_test_payload: shape_class(models.as_any(), "CompiledModelSqlTestPayload")?, - compile_phase: shape_class(types.as_any(), "DiagnosticPhase")?.getattr("COMPILE")?, + diagnostic: registry::class(py, RecordId::CompilerDiagnostic)?, + location: registry::class(py, RecordId::SourceLocation)?, + model_test_payload: registry::class(py, RecordId::CompiledModelSqlTestPayload)?, + compile_phase: registry::class(types.py(), RecordId::DiagnosticPhase)? + .getattr("COMPILE")?, error: severity.getattr("ERROR")?, warning: severity.getattr("WARNING")?, model_resource: resource.getattr("MODEL")?, @@ -64,17 +55,11 @@ impl<'py> StageClasses<'py> { sql_test_resource: resource.getattr("SQL_TEST")?, opt_out_code: PyString::new(py, UNNEEDED_SQL_ANALYSIS_OPT_OUT_CODE).into_any(), replace: py.import(DATACLASSES_MODULE)?.getattr("replace")?, - project_with: py.import(PROJECT_WITH_MODULE)?.getattr("project_with")?, - with_type_recovery: py - .import(TYPE_RECOVERY_MODULE)? - .getattr("with_type_recovery")?, - with_binding_diagnostics: py - .import(BINDING_DIAGNOSTICS_MODULE)? - .getattr("with_binding_diagnostics")?, - schema_validations: py.import(NATIVE_MODULE)?.getattr("schema_validations")?, - normalize_type: py - .import(NORMALIZE_TYPE_MODULE)? - .getattr("normalize_type")?, + project_with: registry::native(py, NativeAttr::ProjectWith)?, + with_type_recovery: registry::native(py, NativeAttr::WithTypeRecovery)?, + with_binding_diagnostics: registry::native(py, NativeAttr::WithBindingDiagnostics)?, + schema_validations: registry::native(py, NativeAttr::SchemaValidations)?, + normalize_type: host::entry(py, AdapterHost::NormalizeType)?, sql_analysis_key: PyString::new(py, SQL_ANALYSIS_CONFIG_KEY).into_any(), udf_kind: kinds.getattr("UDF")?, table_function_kind: kinds.getattr("TABLE_FUNCTION")?, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/semantic_shapes/shapes.rs b/crates/sqlbuild-python/src/bindings/_helpers/semantic_shapes/shapes.rs index 468a2995fe..469b5c98eb 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/semantic_shapes/shapes.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/semantic_shapes/shapes.rs @@ -8,10 +8,11 @@ use crate::bindings::_helpers::analysis::binding_catalog::{ BindingCatalog, CASE_SENSITIVE_BINDING_DIALECTS, }; use crate::bindings::_helpers::boundary::keyword_request::Request; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; -const ADAPTER_MODELS_MODULE: &str = "sqlbuild.adapter.contract.models"; -const PLANNER_TYPES_MODULE: &str = "sqlbuild._native"; -const NATIVE_MODULE: &str = "sqlbuild._native"; const DATACLASSES_MODULE: &str = "dataclasses"; const UNKNOWN: &str = "UNKNOWN"; const GENERIC_DIALECT: &str = "generic"; @@ -72,10 +73,8 @@ pub(crate) fn published_shape<'py>( let py: Python<'py> = model.py(); let shape: Bound<'py, PyDict> = binding_shape(model, columns, inputs, profile)?; let values: Bound<'py, PyAny> = model.getattr("config")?.getattr("values")?; - let snapshot: Bound<'py, PyAny> = py - .import(PLANNER_TYPES_MODULE)? - .getattr("MaterializationType")? - .getattr("SNAPSHOT")?; + let snapshot: Bound<'py, PyAny> = + registry::class(py, RecordId::MaterializationType)?.getattr("SNAPSHOT")?; if values .call_method1("get", ("materialized",))? .eq(snapshot)? @@ -133,10 +132,8 @@ fn binding_shape<'py>( request.set_item("dialect", or_generic(&dialect)?)?; request.set_item("stubs", PyDict::new(py))?; request.set_item("placeholders", PyDict::new(py))?; - let sql: Bound<'py, PyAny> = py - .import(NATIVE_MODULE)? - .getattr("normalize_analysis_sql")? - .call1((request,))?; + let sql: Bound<'py, PyAny> = + registry::native(py, NativeAttr::NormalizeAnalysisSql)?.call1((request,))?; let arguments: Bound<'py, PyDict> = PyDict::new(py); arguments.set_item("sql", sql)?; arguments.set_item("columns", columns)?; @@ -201,9 +198,7 @@ pub(crate) fn project_profile<'py>( "sql_analysis_dialect", project.getattr("sql_analysis_dialect")?, )?; - py.import(ADAPTER_MODELS_MODULE)? - .getattr("ExpressionInferenceProfile")? - .call((), Some(&arguments))? + host::entry(py, AdapterHost::ExpressionInferenceProfile)?.call((), Some(&arguments))? } }; let arguments: Bound<'py, PyDict> = PyDict::new(py); @@ -241,10 +236,8 @@ pub(crate) fn project_shapes<'py>( shapes.set_item(format!("{TABLE_FUNCTION_PREFIX}{name}"), shape)?; } } - let enforced: Bound<'py, PyAny> = py - .import(PLANNER_TYPES_MODULE)? - .getattr("ContractPolicy")? - .getattr("ENFORCED")?; + let enforced: Bound<'py, PyAny> = + registry::class(py, RecordId::ContractPolicy)?.getattr("ENFORCED")?; let mut expression_names: Vec> = Vec::new(); let mut expressions: Vec> = Vec::new(); for source in project.getattr("sources")?.try_iter()? { @@ -266,10 +259,9 @@ pub(crate) fn project_shapes<'py>( let arguments: Bound<'py, PyDict> = PyDict::new(py); arguments.set_item("expressions", PyTuple::new(py, expressions)?)?; arguments.set_item("profile", &profile)?; - let source_shapes: Bound<'py, PyAny> = py - .import(NATIVE_MODULE)? - .getattr("infer_native_expression_source_shapes")? - .call((), Some(&arguments))?; + let source_shapes: Bound<'py, PyAny> = + registry::native(py, NativeAttr::InferNativeExpressionSourceShapes)? + .call((), Some(&arguments))?; for (name, shape) in expression_names.iter().zip(source_shapes.try_iter()?) { let shape: Bound<'py, PyAny> = shape?; if !shape.is_none() { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/semantic_shapes/source_bindings.rs b/crates/sqlbuild-python/src/bindings/_helpers/semantic_shapes/source_bindings.rs index f05b78d6f4..976de6687d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/semantic_shapes/source_bindings.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/semantic_shapes/source_bindings.rs @@ -2,7 +2,6 @@ use std::collections::HashSet; -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyBool, PyDict, PyDictMethods, PyString, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; @@ -15,6 +14,8 @@ use crate::bindings::_helpers::analysis::schema_request::{ }; use crate::bindings::_helpers::attachments::cursor_intrinsics::analysis_cursor_sql; use crate::bindings::_helpers::boundary::keyword_request::Request; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::semantic_shapes::declarations::{declared_types, function_names}; use crate::bindings::_helpers::semantic_shapes::shapes::{ model_inputs, project_shapes, published_shape, @@ -22,9 +23,6 @@ use crate::bindings::_helpers::semantic_shapes::shapes::{ const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; -const NATIVE_MODULE: &str = "sqlbuild._native"; -const ANALYSIS_MODELS_MODULE: &str = "sqlbuild._native"; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const LINEAGE_TYPES_MODULE: &str = "sqlbuild.compiler.lineage.types"; const BINDING_UNKNOWN_TABLE_INTERNAL_CODE: &str = "B000"; const DATACLASSES_MODULE: &str = "dataclasses"; @@ -157,16 +155,12 @@ fn rebound_models<'py>( .getattr("TopologicalSorter")? .call1((graph,))? .call_method0("static_order")?; - let query_class: Object<'py> = py - .import(ANALYSIS_MODELS_MODULE)? - .getattr("NativeColumnQuery")?; + let query_class: Object<'py> = registry::class(py, RecordId::NativeColumnQuery)?; let unknown: Object<'py> = py .import(LINEAGE_TYPES_MODULE)? .getattr("InferredNullability")? .getattr("UNKNOWN")?; - let infer: Object<'py> = py - .import(NATIVE_MODULE)? - .getattr("infer_native_query_columns")?; + let infer: Object<'py> = registry::native(py, NativeAttr::InferNativeQueryColumns)?; let mut affected: HashSet = HashSet::new(); let mut models: Vec> = Vec::new(); for name in order.try_iter()? { @@ -307,11 +301,11 @@ fn validated_diagnostics<'py>( expansions.set_item(path.getattr("stem")?, expansion)?; } let skipped: Object<'py> = PyString::new(py, BINDING_UNKNOWN_TABLE_INTERNAL_CODE).into_any(); - let models_module = py.import(COMPILE_MODELS_MODULE)?; let types = py.import(COMPILE_TYPES_MODULE)?; - let diagnostic_class: Object<'py> = models_module.getattr("CompilerDiagnostic")?; - let severity_class: Object<'py> = shape_class(types.as_any(), "DiagnosticSeverity")?; - let phase: Object<'py> = shape_class(types.as_any(), "DiagnosticPhase")?.getattr("COMPILE")?; + let diagnostic_class: Object<'py> = registry::class(py, RecordId::CompilerDiagnostic)?; + let severity_class: Object<'py> = registry::class(types.py(), RecordId::DiagnosticSeverity)?; + let phase: Object<'py> = + registry::class(types.py(), RecordId::DiagnosticPhase)?.getattr("COMPILE")?; let model_type: Object<'py> = types.getattr("CompiledResourceType")?.getattr("MODEL")?; let mut diagnostics: Vec> = Vec::new(); for ((model, request), result) in models.iter().zip(&requests).zip(results.try_iter()?) { @@ -395,9 +389,7 @@ fn positions<'py>( request.set_item("expanded", expanded)?; request.set_item("cleaned", cleaned)?; request.set_item("passes", passes)?; - py.import(NATIVE_MODULE)? - .getattr("BindingPositions")? - .call1((request,)) + registry::native(py, NativeAttr::BindingPositions)?.call1((request,)) } /// Whether inspected columns differ from the known shape; untyped names match any type. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_command/test_command.rs b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_command/test_command.rs index f9711d0da0..4019dbc047 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_command/test_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_command/test_command.rs @@ -25,22 +25,15 @@ use sqlbuild_core::test_command::models::{ use crate::bindings::_helpers::build_progress::transient_status::status_reporter; use crate::bindings::_helpers::command_runtime::invocation::resolve_reported_command_invocation; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::compile_outputs::runtime_target::write_test_runtime_target; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::result_output::integration_values::json_value; use crate::bindings::_helpers::result_output::resource_ids::sql_test_identity; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const NATIVE_MODULE: &str = "sqlbuild._native"; -const EXTERNAL_REFS_MODULE: &str = "sqlbuild._native"; -const STATIC_COMMAND_MODULE: &str = "sqlbuild._native"; -const TEST_PLAN_MODULE: &str = "sqlbuild._native"; -const PLANNER_ERRORS: &str = "sqlbuild._native"; -const EXECUTOR_PIPELINE_MODELS: &str = "sqlbuild.executor.pipeline.models"; -const EXECUTOR_RUN_MODULE: &str = "sqlbuild.executor.pipeline.main.run"; const DECIMAL_KIND: &str = "decimal"; const PASS_OUTCOME: &str = "pass"; const EXPECT_LABEL: &str = "expect"; @@ -153,9 +146,9 @@ fn outcome_status(outcome: &Bound<'_, PyAny>) -> PyResult<&'static str> { /// Nested `expect` rows of a completed test, one per step. fn expectation_rows<'py>(result: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = result.py(); - let row_class: Bound<'py, PyAny> = imported(py, NATIVE_MODULE, "NestedProgressChildRow")?; - let name: Bound<'py, PyAny> = imported(py, NATIVE_MODULE, "format_expectation_name")?; - let detail: Bound<'py, PyAny> = imported(py, NATIVE_MODULE, "format_expectation_detail")?; + let row_class: Bound<'py, PyAny> = registry::class(py, RecordId::NestedProgressChildRow)?; + let name: Bound<'py, PyAny> = registry::native(py, NativeAttr::FormatExpectationName)?; + let detail: Bound<'py, PyAny> = registry::native(py, NativeAttr::FormatExpectationDetail)?; let mut rows: Vec> = Vec::new(); for step in result.getattr("step_results")?.try_iter()? { let step: Bound<'py, PyAny> = step?; @@ -296,7 +289,7 @@ fn unknown_case(py: Python<'_>, message: String) -> PyErr { let raised = (|| { let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("code", UNKNOWN_CASE_CODE)?; - imported(py, PLANNER_ERRORS, "PlannerInputError")?.call((message,), Some(&options)) + registry::error(py, ErrorId::PlannerInput)?.call((message,), Some(&options)) })(); match raised { Ok(error) => PyErr::from_value(error), @@ -313,18 +306,14 @@ fn compile_tests<'py>( let discovered: Bound<'py, PyAny> = invocation.getattr("discovered_inputs")?; let adapter: Bound<'py, PyAny> = invocation.getattr("adapter")?; let resolver: Bound<'py, PyAny> = call_with( - &imported( - py, - EXTERNAL_REFS_MODULE, - "resolve_external_sql_reference_resolver", - )?, + ®istry::native(py, NativeAttr::ResolveExternalSqlReferenceResolver)?, &[ ("project_dir", invocation.getattr("effective_project_dir")?), ("discovered_inputs", discovered.clone()), ], )?; let options: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_RECORDS_MODULE, "CompilePipelineOptions")?, + ®istry::class(py, RecordId::CompilePipelineOptions)?, &[ ("selected_target", request.getattr("selected_target")?), ("no_sql_validation", request.getattr("no_sql_validation")?), @@ -347,7 +336,7 @@ fn compile_tests<'py>( ], )?; let context: Bound<'py, PyAny> = call_with( - &imported(py, STATIC_COMMAND_MODULE, "compile_static_command_context")?, + ®istry::native(py, NativeAttr::CompileStaticCommandContext)?, &[ ("discovered_inputs", discovered), ("adapter", adapter.clone()), @@ -362,7 +351,7 @@ fn compile_tests<'py>( )?; let case_name: Bound<'py, PyAny> = request.getattr("case_name")?; let plan_output: Bound<'py, PyAny> = call_with( - &imported(py, TEST_PLAN_MODULE, "build_test_command_plan")?, + ®istry::native(py, NativeAttr::BuildTestCommandPlan)?, &[ ("project", context.getattr("project")?), ("adapter", adapter), @@ -387,7 +376,7 @@ fn compile_tests<'py>( )); } call_with( - &imported(py, NATIVE_MODULE, "build_static_pipeline_result")?, + ®istry::native(py, NativeAttr::BuildStaticPipelineResult)?, &[("context", context), ("plan_output", plan_output)], ) } @@ -475,7 +464,7 @@ fn run_tests<'py>( } let worker_count: i64 = concurrency.min(i64::try_from(test_count).unwrap_or(i64::MAX)); let progress: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "NestedCommandProgressCallbacks")?, + ®istry::native(py, NativeAttr::NestedCommandProgressCallbacks)?, &[ ("total", PyInt::new(py, test_count).into_any()), ("label", text(py, "test")), @@ -489,7 +478,7 @@ fn run_tests<'py>( ], )?; let projector: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "current_native_progress_projector")?.call0()?; + registry::native(py, NativeAttr::CurrentNativeProgressProjector)?.call0()?; if !projector.is_none() { for entry in entries.try_iter()? { call_with( @@ -504,7 +493,7 @@ fn run_tests<'py>( )?; stream.call_method0("flush")?; let connection: Bound<'py, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "ConnectionProgressReporter")?, + ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, &[ ("adapter_name", invocation.getattr("adapter_name")?), ("stream", stream.clone()), @@ -513,7 +502,7 @@ fn run_tests<'py>( )?; let preflight: Bound<'py, PyAny> = status_reporter(&stream, use_color.is_truthy()?)?; let event_writer: Bound<'py, PyAny> = - imported(py, NATIVE_MODULE, "ExecutionEventWriter")?.call0()?; + registry::native(py, NativeAttr::ExecutionEventWriter)?.call0()?; let relay: Bound<'py, TestCallbackRelay> = Bound::new( py, TestCallbackRelay { @@ -525,7 +514,7 @@ fn run_tests<'py>( )?; let executed: PyResult>> = (|| { let callbacks: Bound<'py, PyAny> = call_with( - &imported(py, EXECUTOR_PIPELINE_MODELS, "TestPipelineCallbacks")?, + &host::entry(py, ExecutorHost::TestPipelineCallbacks)?, &[ ( "on_connection_start", @@ -545,7 +534,7 @@ fn run_tests<'py>( ], )?; call_with( - &imported(py, EXECUTOR_RUN_MODULE, "run_test_pipeline")?, + &host::entry(py, ExecutorHost::RunTestPipeline)?, &[ ("plan", plan_output), ( @@ -607,11 +596,11 @@ fn complete( stream.call_method0("flush")?; let tuple: Bound<'_, PyAny> = PyTuple::new(py, results)?.into_any(); let payload: Bound<'_, PyAny> = call_with( - &imported(py, NATIVE_MODULE, "format_test_execution_json")?, + ®istry::native(py, NativeAttr::FormatTestExecutionJson)?, &[("results", tuple)], )?; call_with( - &imported(py, NATIVE_MODULE, "write_execution_json_output")?, + ®istry::native(py, NativeAttr::WriteExecutionJsonOutput)?, &[ ("payload", payload), ("json_output", request.getattr("json_output")?), @@ -628,7 +617,7 @@ fn run_test_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult let inspect_only: bool = request.getattr("inspect")?.is_truthy()?; let invocation: Bound<'_, PyAny> = resolve_reported_command_invocation( request, - &imported(py, NATIVE_MODULE, "TestInvocation")?, + ®istry::class(py, RecordId::TestInvocation)?, !inspect_only, )?; let pipeline: Bound<'_, PyAny> = compile_tests(request, &invocation)?; @@ -653,7 +642,7 @@ fn run_test_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult header.set_item("target", py.None())?; header.set_item("concurrency", concurrency)?; header.set_item("use_color", invocation.getattr("use_color")?)?; - imported(py, NATIVE_MODULE, "write_execution_header")?.call((), Some(&header))?; + registry::native(py, NativeAttr::WriteExecutionHeader)?.call((), Some(&header))?; let results: Vec> = run_tests(&invocation, &pipeline, concurrency)?; write_test_runtime_target( ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/authored_ctes.rs b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/authored_ctes.rs index f1b7ed40f4..618ed0562d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/authored_ctes.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/authored_ctes.rs @@ -12,10 +12,10 @@ use sqlbuild_analysis::compiler::models::{SqlTestExtractionFailure, SqlTestReque use sqlbuild_analysis::sql_test_attachment::main::authored_test_offset::authored_test_offset; use sqlbuild_sqltext::sql_scan::models::LexicalSyntax; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sqltext::lexical_syntax::LexicalSyntaxInput; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; -const COMPILE_INPUT_ERROR: &str = "CompileInputError"; const MODEL_MODE: &str = "model"; /// Extract fixture bodies from authored test or scenario CTEs in authored order. @@ -81,10 +81,8 @@ fn located_error(py: Python<'_>, failure: &SqlTestExtractionFailure, body: &Auth let raised = || -> PyResult { let keywords: Bound<'_, PyDict> = PyDict::new(py); keywords.set_item("help", failure.help.as_deref())?; - let error: Bound<'_, PyAny> = py - .import(COMPILE_ERRORS_MODULE)? - .getattr(COMPILE_INPUT_ERROR)? - .call((message,), Some(&keywords))?; + let error: Bound<'_, PyAny> = + registry::error(py, ErrorId::CompileInput)?.call((message,), Some(&keywords))?; Ok(PyErr::from_value(error)) }; raised().unwrap_or_else(|error| error) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/compiled_tests.rs b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/compiled_tests.rs index fd84264b42..71b4cf56b0 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/compiled_tests.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/compiled_tests.rs @@ -1,19 +1,17 @@ //! Compiled SQL tests, their keyed P013 diagnostics and assembly failures, built natively. -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyString, PyTuple}; use pyo3::{IntoPyObject, pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::boundary::panics::compiler_error; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sql_test_glue::assembly::{ AssemblyRequest, AssemblyRow, DiagnosticRow, FactsRow, assemble_compiled_sql_tests, }; -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; -const GLUE_MODELS_MODULE: &str = "sqlbuild._native"; -const SPEC_MODELS_MODULE: &str = "sqlbuild._native"; /// Fields a direct-logic compiled payload copies from its input payload, in order. const DIRECT_PAYLOAD_FIELDS: [&str; 5] = [ "mode", @@ -50,7 +48,6 @@ type Fields<'py> = Vec<(&'static str, Bound<'py, PyAny>)>; /// The Python classes a compiled test is built from. struct Classes<'py> { - models: Bound<'py, PyModule>, resource_type: Bound<'py, PyAny>, direct_input: Bound<'py, PyAny>, assembly: Bound<'py, PyAny>, @@ -63,33 +60,30 @@ struct Classes<'py> { impl<'py> Classes<'py> { fn new(py: Python<'py>) -> PyResult { - let models: Bound<'py, PyModule> = py.import(COMPILE_MODELS_MODULE)?; let types: Bound<'py, PyModule> = py.import(COMPILE_TYPES_MODULE)?; - let glue: Bound<'py, PyModule> = py.import(GLUE_MODELS_MODULE)?; Ok(Self { - direct_input: shape_class(&models, "CompileDirectLogicSqlTestInputPayload")?, + direct_input: registry::class(py, RecordId::CompileDirectLogicSqlTestInputPayload)?, resource_type: types.getattr("CompiledResourceType")?, - phase: shape_class(&types, "DiagnosticPhase")?.getattr("COMPILE")?, - severity: shape_class(&types, "DiagnosticSeverity")?.getattr("ERROR")?, - assembly: glue.getattr("NativeSqlTestAssembly")?, - failure: glue.getattr("NativeSqlTestFailure")?, - location: py.import(SPEC_MODELS_MODULE)?.getattr("SourceLocation")?, + phase: registry::class(py, RecordId::DiagnosticPhase)?.getattr("COMPILE")?, + severity: registry::class(py, RecordId::DiagnosticSeverity)?.getattr("ERROR")?, + assembly: registry::class(py, RecordId::NativeSqlTestAssembly)?, + failure: registry::class(py, RecordId::NativeSqlTestFailure)?, + location: registry::class(py, RecordId::SourceLocation)?, code: PyString::new(py, super::constants::SQL_TEST_HELPER_REFERENCE_CODE).into_any(), - models, }) } fn py(&self) -> Python<'py> { - self.models.py() + self.resource_type.py() } - fn model(&self, name: &str) -> PyResult> { - shape_class(&self.models, name) + fn model(&self, name: RecordId) -> PyResult> { + registry::class(self.py(), name) } fn object_key(&self, resource_type: &str, name: &str) -> PyResult> { keyword_call( - &self.model("CompiledObjectKey")?, + &self.model(RecordId::CompiledObjectKey)?, vec![ ("resource_type", self.resource_type.call1((resource_type,))?), ("name", text(self.py(), name)), @@ -131,10 +125,10 @@ fn compiled_payload<'py>( let py: Python<'py> = classes.py(); if payload.is_instance(&classes.direct_input)? { let compiled: Bound<'py, PyAny> = keyword_call( - &classes.model("CompiledDirectLogicSqlTestPayload")?, + &classes.model(RecordId::CompiledDirectLogicSqlTestPayload)?, copied(payload, &DIRECT_PAYLOAD_FIELDS)?, )?; - let resource_type: Bound<'py, PyAny> = classes.model("CompiledSqlTestResource")?; + let resource_type: Bound<'py, PyAny> = classes.model(RecordId::CompiledSqlTestResource)?; let mode: Bound<'py, PyAny> = payload.getattr("mode")?; let mut resources: Vec> = Vec::new(); for name in payload.getattr("tested_resource_names")?.try_iter()? { @@ -154,7 +148,7 @@ fn compiled_payload<'py>( copied(payload, &MODEL_PAYLOAD_FIELDS)?; payload_fields.push(("model_query_overrides", PyDict::new(py).into_any())); let compiled: Bound<'py, PyAny> = keyword_call( - &classes.model("CompiledModelSqlTestPayload")?, + &classes.model(RecordId::CompiledModelSqlTestPayload)?, payload_fields, )?; let mut fields: Vec<(&'static str, Bound<'py, PyAny>)> = copied(payload, &MODEL_TEST_FIELDS)?; @@ -190,7 +184,7 @@ fn keyed_diagnostic<'py>( ], )?; let diagnostic: Bound<'py, PyAny> = keyword_call( - &classes.model("CompilerDiagnostic")?, + &classes.model(RecordId::CompilerDiagnostic)?, vec![ ("phase", classes.phase.clone()), ("severity", classes.severity.clone()), @@ -249,7 +243,7 @@ fn compiled_test<'py>( ), ]; fields.extend(payload_fields); - let test: Bound<'py, PyAny> = keyword_call(&classes.model("CompiledSqlTest")?, fields)?; + let test: Bound<'py, PyAny> = keyword_call(&classes.model(RecordId::CompiledSqlTest)?, fields)?; let keyed: Vec> = diagnostics .into_iter() .map(|row| keyed_diagnostic(classes, (test_input, &resource_name), row)) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/constants.rs index 463bd380c9..f23145f868 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/constants.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/constants.rs @@ -4,8 +4,6 @@ pub(super) const UDF: &str = "udf"; pub(super) const TABLE_FN: &str = "table_fn"; pub(super) const ERROR_SEVERITY: &str = "error"; pub(super) const COMPILE_TYPES: &str = "sqlbuild.compiler.compile.types"; -pub(super) const PLAN_MODELS: &str = "sqlbuild._native"; -pub(super) const NATIVE_PLANNING: &str = "sqlbuild._native"; pub(super) const FIXTURE_PREFIXES: [(&str, &str); 4] = [ ("model", "__ref__"), ("source", "__source__"), @@ -13,7 +11,6 @@ pub(super) const FIXTURE_PREFIXES: [(&str, &str); 4] = [ ("sql_test", "__expected__"), ]; pub(super) const CURSOR_ALGEBRA_EXCEPTIONS: &str = "sqlbuild.cursor_algebra.exceptions"; -pub(super) const PLANNER_TYPES: &str = "sqlbuild._native"; pub(super) const INTEGER_CURSOR_TYPE: &str = "integer"; pub(super) const TIMESTAMP_CURSOR_TYPE: &str = "timestamp"; pub(super) const DEFAULT_TIMESTAMP_START: &str = "1900-01-01 00:00:00"; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/cursor_window.rs b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/cursor_window.rs index 6c1ae72432..872e7c9fc0 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/cursor_window.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/cursor_window.rs @@ -15,10 +15,12 @@ use crate::bindings::_helpers::compile_driver::python_calls::{call_with, importe use crate::bindings::_helpers::cursor::conversion::CursorSession; use crate::bindings::_helpers::cursor::scalars::{parse_value, render_value}; use crate::bindings::_helpers::model_config::config_values::config_str; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sql_test_glue::constants::{ CURSOR_ALGEBRA_EXCEPTIONS, CURSOR_END_KEY, CURSOR_START_KEY, DEFAULT_INTEGER_END, DEFAULT_INTEGER_START, DEFAULT_TIMESTAMP_END, DEFAULT_TIMESTAMP_START, INTEGER_CURSOR_TYPE, - PLANNER_TYPES, TIMESTAMP_CURSOR_TYPE, + TIMESTAMP_CURSOR_TYPE, }; /// One model's cursor name, type and timestamp grain. @@ -237,13 +239,13 @@ fn model_cursor<'py>(model: &Bound<'py, PyAny>) -> PyResult> { .extract::() .is_ok_and(|value| !value.is_empty()) }); - let cursor_type = imported(py, PLANNER_TYPES, "CursorType")?.call1(( + let cursor_type = registry::class(py, RecordId::CursorType)?.call1(( declared.unwrap_or_else(|| PyString::new(py, TIMESTAMP_CURSOR_TYPE).into_any()), ))?; let type_name: String = cursor_type.getattr("value")?.extract()?; let grain = match config_str(&values, "cursor_grain")? { Some(grain) if type_name == TIMESTAMP_CURSOR_TYPE => { - let grain = imported(py, PLANNER_TYPES, "CursorGrain")?.call1((grain,))?; + let grain = registry::class(py, RecordId::CursorGrain)?.call1((grain,))?; let session = CursorSession::new(py)?; session .grain(&grain)? diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_assembly.rs b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_assembly.rs index 0075483932..f88373bfe9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_assembly.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_assembly.rs @@ -3,10 +3,9 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python, pyfunction}; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; -use crate::bindings::_helpers::sql_test_glue::constants::NATIVE_PLANNING; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; #[pyfunction(name = "plan_sql_tests")] #[pyo3(signature = (**kwargs))] @@ -36,7 +35,7 @@ pub(crate) fn assemble_sql_test_plans<'py>( context = args.value("fixture_planning_context")?; if !context.is_truthy()? { context = call_with( - &imported(py, "sqlbuild._native", "build_relation_fixture_context")?, + ®istry::native(py, NativeAttr::BuildRelationFixtureContext)?, &[("project", project.clone())], )?; } @@ -50,7 +49,7 @@ pub(crate) fn assemble_sql_test_plans<'py>( .filter_map(|(index, test)| (!diagnostics.contains_key(&index)).then_some(test)) .collect(); let plans = call_with( - &imported(py, NATIVE_PLANNING, "plan_sql_tests_natively")?, + ®istry::native(py, NativeAttr::PlanSqlTestsNatively)?, &[ ("project", project.clone()), ("tests", PyTuple::new(py, plannable)?.into_any()), @@ -77,7 +76,7 @@ pub(crate) fn assemble_sql_test_plans<'py>( let model = model?; models.set_item(model.getattr("name")?, model)?; } - let result_class = imported(py, "sqlbuild._native", "SqlTestPlanResult")?; + let result_class = registry::class(py, RecordId::SqlTestPlanResult)?; let mut results: Vec> = Vec::new(); for (index, test) in tests.iter().enumerate() { if let Some(diagnostic) = diagnostics.get(&index) { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_driver.rs b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_driver.rs index 4652f20271..a9095ed179 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_driver.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_driver.rs @@ -4,6 +4,8 @@ use crate::bindings::_helpers::compile_driver::compile_timings::add_compile_timi use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, }; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sql_test_glue::plan_values::tuple; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python, pyfunction}; use pyo3::types::{PyBool, PyDict, PyTuple}; @@ -17,7 +19,7 @@ pub(super) fn chains<'py>( return Ok(PyTuple::empty(py).into_any()); } let request = call_with( - &imported(py, "sqlbuild._native", "NativeSqlTestChainRequest")?, + ®istry::class(py, RecordId::NativeSqlTestChainRequest)?, &[ ("models", project.getattr("models")?), ("tests", tests.clone()), @@ -29,8 +31,7 @@ pub(super) fn chains<'py>( ), ], )?; - let native = - imported(py, "sqlbuild._native", "resolve_compiled_sql_test_chains")?.call1((request,)); + let native = registry::native(py, NativeAttr::ResolveCompiledSqlTestChains)?.call1((request,)); let native = match native { Ok(value) => value, Err(error) if error.is_instance_of::(py) => { @@ -75,7 +76,7 @@ fn planned<'py>(args: &KeywordArguments<'py>, artifact: bool) -> PyResult(args: &KeywordArguments<'py>, artifact: bool) -> PyResult value, Err(error) if error.is_instance_of::(py) => { @@ -151,12 +152,12 @@ fn planned<'py>(args: &KeywordArguments<'py>, artifact: bool) -> PyResult(test: &Bound<'py, PyAny>, prefix: &str) -> PyResult> { @@ -124,7 +126,7 @@ pub(super) fn entry<'py>( fields.set_item("chain", plan.getattr("chain")?)?; fields.set_item("assertions", plan.getattr("assertions")?)?; fields.set_item("sql_analysis_enabled", analysis)?; - imported(py, PLAN_MODELS, "SqlTestPlanEntry")?.call((), Some(&fields)) + registry::class(py, RecordId::SqlTestPlanEntry)?.call((), Some(&fields)) } pub(super) fn expected_diagnostics( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_errors.rs b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_errors.rs index 043e11832f..86881e4082 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_errors.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_errors.rs @@ -1,14 +1,14 @@ //! Project native planning failures into existing located diagnostic exceptions. use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sql_test_glue::plan_values::string; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods}; use sqlbuild_analysis::sql_test_attachment::main::sql_test_cte_location::sql_test_cte_location; use sqlbuild_analysis::sql_test_attachment::models::CteLocation; -const SOURCE_LOCATION_MODULE: &str = "sqlbuild._native"; - /// The location of `call` inside one CTE of a SQL test block, else the CTE header, else the block. fn cte_call_location<'py>( py: Python<'py>, @@ -32,7 +32,7 @@ fn cte_call_location<'py>( arguments.set_item("column", located.column)?; arguments.set_item("end_line", located.end_line)?; arguments.set_item("end_column", located.end_column)?; - imported(py, SOURCE_LOCATION_MODULE, "SourceLocation")?.call((), Some(&arguments)) + registry::class(py, RecordId::SourceLocation)?.call((), Some(&arguments)) } pub(super) fn planning_error( @@ -73,27 +73,26 @@ pub(super) fn planning_error( kwargs.set_item("help", help)?; let class = if let Some(test) = found { kwargs.set_item("location", cte_call_location(py, &test, (&cte, &call))?)?; - "SqlTestReferenceError" + ErrorId::SqlTestReference } else { kwargs.set_item("code", super::constants::SQL_TEST_HELPER_REFERENCE_CODE)?; - "CompileInputError" + ErrorId::CompileInput }; return Ok(PyErr::from_value( - imported(py, "sqlbuild._native", class)?.call((message,), Some(&kwargs))?, + registry::error(py, class)?.call((message,), Some(&kwargs))?, )); } - let (module, class, message) = if let Some(message) = message.strip_prefix("compile_input:") { - ("sqlbuild._native", "CompileInputError", message.to_owned()) + let (class, message) = if let Some(message) = message.strip_prefix("compile_input:") { + (ErrorId::CompileInput, message.to_owned()) } else if let Some(message) = message.strip_prefix("planner_input:") { - ("sqlbuild._native", "PlannerInputError", message.to_owned()) + (ErrorId::PlannerInput, message.to_owned()) } else { ( - "sqlbuild._native", - "NativeSqlTestPlanningError", + ErrorId::NativeSqlTestPlanning, format!("native SQL-test planning failed: {message}"), ) }; Ok(PyErr::from_value( - imported(py, module, class)?.call1((string(py, &message),))?, + registry::error(py, class)?.call1((string(py, &message),))?, )) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_fixtures.rs b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_fixtures.rs index df8734947d..78a9c510a2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_fixtures.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_fixtures.rs @@ -4,8 +4,9 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::records::ids::NativeAttr; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sql_test_glue::authored_ctes::prefixed_ctes; -use crate::bindings::_helpers::sql_test_glue::constants::NATIVE_PLANNING; use crate::bindings::_helpers::sql_test_glue::fixture_validation::validated_fixtures; use crate::bindings::_helpers::sql_test_glue::plan_values::model_payload; @@ -92,7 +93,7 @@ pub(super) fn validate<'py>( return Ok((tests.to_vec(), Default::default())); } let chains = call_with( - &imported(py, NATIVE_PLANNING, "resolve_sql_test_model_chains")?, + ®istry::native(py, NativeAttr::ResolveSqlTestModelChains)?, &[ ("project", inputs.0.clone()), ("tests", PyTuple::new(py, model_tests)?.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_rendering.rs b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_rendering.rs index 1a4a6e7599..350760a6b0 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_rendering.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_rendering.rs @@ -1,10 +1,12 @@ //! Adapter rendering and declared cursor-window boundaries for native test planning. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::sql_test_glue::plan_values::{model_payload, string}; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult}; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sql_test_glue::constants::SENTINEL; use crate::bindings::_helpers::sql_test_glue::cursor_window::{ declared_window_cursor_models, declares_cursor_window, render_test_cursor_intrinsics, @@ -160,7 +162,7 @@ pub(super) fn functions<'py>( .filter(|(_, suffix)| !suffix.contains(SENTINEL)) else { return Err(PyErr::from_value( - imported(py, "sqlbuild._native", "NativeSqlTestPlanningError")? + registry::error(py, ErrorId::NativeSqlTestPlanning)? .call1(("adapter SQL function call template is not deterministic",))?, )); }; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_rows.rs b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_rows.rs index a428435e0c..9976e742c1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_rows.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_rows.rs @@ -1,8 +1,8 @@ //! Materialize native plan rows as the existing executor-facing dataclasses. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class_at; -use crate::bindings::_helpers::sql_test_glue::constants::PLAN_MODELS; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sql_test_glue::plan_values::tuple; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; @@ -36,13 +36,12 @@ fn step<'py>(row: &Bound<'py, PyAny>, chain: bool) -> PyResult }, )?; } - shape_class_at( + registry::class( py, - PLAN_MODELS, if chain { - "ChainStep" + RecordId::ChainStep } else { - "SqlTestAssertionStep" + RecordId::SqlTestAssertionStep }, )? .call((), Some(&kwargs)) @@ -59,11 +58,11 @@ pub(super) fn plan<'py>(row: &Bound<'py, PyAny>) -> PyResult> assertions.push(step(&value?, false)?); } let mut warnings: Vec> = Vec::new(); - let severity = imported(py, "sqlbuild._native", "WarningSeverity")?; + let severity = registry::class(py, RecordId::WarningSeverity)?; for value in row.get_item(4)?.try_iter()? { let value = value?; warnings.push(call_with( - &imported(py, "sqlbuild._native", "PlanWarning")?, + ®istry::class(py, RecordId::PlanWarning)?, &[ ("model_name", value.get_item(0)?), ("severity", severity.call1((value.get_item(1)?,))?), @@ -72,7 +71,7 @@ pub(super) fn plan<'py>(row: &Bound<'py, PyAny>) -> PyResult> )?); } call_with( - &imported(py, "sqlbuild._native", "NativeSqlTestPlan")?, + ®istry::class(py, RecordId::NativeSqlTestPlan)?, &[ ("chain", PyTuple::new(py, chain)?.into_any()), ("assertions", PyTuple::new(py, assertions)?.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_values.rs b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_values.rs index dcdcf61198..970a81e3e3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_values.rs @@ -4,18 +4,17 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::PyString; use crate::bindings::_helpers::compile_driver::python_calls::imported; - -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; pub(super) fn string<'py>(py: Python<'py>, value: &str) -> Bound<'py, PyAny> { PyString::new(py, value).into_any() } pub(super) fn model_payload(test: &Bound<'_, PyAny>) -> PyResult { - test.getattr("payload")?.is_instance(&imported( + test.getattr("payload")?.is_instance(®istry::class( test.py(), - NATIVE_RECORDS_MODULE, - "CompiledModelSqlTestPayload", + RecordId::CompiledModelSqlTestPayload, )?) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sqltext/authored_values.rs b/crates/sqlbuild-python/src/bindings/_helpers/sqltext/authored_values.rs index 77659c1912..6a7f5ed88e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sqltext/authored_values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sqltext/authored_values.rs @@ -1,14 +1,12 @@ //! Authored header values as the Python header objects: scalars, sets, constants and hooks. -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class_at; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use pyo3::prelude::{IntoPyObject, Py, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyList, PyTuple}; use sqlbuild_sqltext::compiler::main::header_word_value::header_word_value; use sqlbuild_sqltext::compiler::models::HeaderWord; -const SQL_VALUES_MODULE: &str = "sqlbuild._native"; -const DISCOVERY_MODELS_MODULE: &str = "sqlbuild._native"; - fn word_to_python(py: Python<'_>, word: String) -> PyResult> { match header_word_value(&word) { HeaderWord::Bool(value) => Ok(value.into_pyobject(py)?.to_owned().unbind().into_any()), @@ -28,10 +26,10 @@ fn word_to_python(py: Python<'_>, word: String) -> PyResult> { fn instance( py: Python<'_>, - (module, class): (&str, &str), + class: RecordId, arguments: &pyo3::Bound<'_, PyDict>, ) -> PyResult> { - Ok(shape_class_at(py, module, class)? + Ok(registry::class(py, class)? .call((), Some(arguments))? .unbind()) } @@ -60,7 +58,7 @@ fn constant_call( .collect::>>()?; let keywords = PyDict::new(py); keywords.set_item("arguments", PyTuple::new(py, arguments)?)?; - instance(py, (SQL_VALUES_MODULE, "AuthoredSqlValueCall"), &keywords) + instance(py, RecordId::AuthoredSqlValueCall, &keywords) } fn authored_value_to_python( @@ -87,17 +85,21 @@ fn authored_value_to_python( AuthoredValue::Set(values) => { let keywords = PyDict::new(py); keywords.set_item("values", values_tuple(py, values)?)?; - instance(py, (SQL_VALUES_MODULE, "AuthoredSqlSet"), &keywords) + instance(py, RecordId::AuthoredSqlSet, &keywords) } AuthoredValue::Tuple(values) => values_tuple(py, values), AuthoredValue::TypedConstant(values) => constant_call(py, values), AuthoredValue::InlineSqlHook(statement) => { let keywords = PyDict::new(py); keywords.set_item("statement", statement)?; - instance(py, (DISCOVERY_MODELS_MODULE, "SqlHookEntry"), &keywords) + instance(py, RecordId::SqlHookEntry, &keywords) + } + AuthoredValue::NamedSqlHook(name, kwargs) => { + hook(py, RecordId::NamedSqlHookEntry, name, kwargs) + } + AuthoredValue::PythonHook(name, kwargs) => { + hook(py, RecordId::PythonHookEntry, name, kwargs) } - AuthoredValue::NamedSqlHook(name, kwargs) => hook(py, "NamedSqlHookEntry", name, kwargs), - AuthoredValue::PythonHook(name, kwargs) => hook(py, "PythonHookEntry", name, kwargs), } } @@ -114,14 +116,14 @@ pub(crate) fn map_to_python( fn hook( py: Python<'_>, - class: &str, + class: RecordId, name: String, kwargs: Vec<(String, sqlbuild_sqltext::compiler::models::AuthoredValue)>, ) -> PyResult> { let keywords = PyDict::new(py); keywords.set_item("name", name)?; keywords.set_item("kwargs", map_to_python(py, kwargs)?)?; - instance(py, (DISCOVERY_MODELS_MODULE, class), &keywords) + instance(py, class, &keywords) } pub(crate) fn optional_authored_value_to_python( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sqltext/code_scanner.rs b/crates/sqlbuild-python/src/bindings/_helpers/sqltext/code_scanner.rs index 594654873d..bbcc55a893 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sqltext/code_scanner.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sqltext/code_scanner.rs @@ -10,11 +10,10 @@ use sqlbuild_sqltext::code_scanner::main::scanner_non_code_end::scanner_non_code use sqlbuild_sqltext::code_scanner::models::CodeScanner; use sqlbuild_sqltext::sql_scan::models::Unclosed; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sqltext::lexical_syntax::LexicalSyntaxInput; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; -const COMPILE_INPUT_ERROR: &str = "CompileInputError"; - /// One SQL text scanned under an adapter's lexical rules, or the generic compiler rules. #[pyclass(module = "sqlbuild._native", frozen)] pub(crate) struct SqlCodeScanner { @@ -54,10 +53,8 @@ impl SqlCodeScanner { }; let message: String = format!("{} contains an unclosed {construct}", self.context); let raised = || -> PyResult { - let error: Bound<'_, PyAny> = py - .import(COMPILE_ERRORS_MODULE)? - .getattr(COMPILE_INPUT_ERROR)? - .call1((message,))?; + let error: Bound<'_, PyAny> = + registry::error(py, ErrorId::CompileInput)?.call1((message,))?; Ok(PyErr::from_value(error)) }; raised().unwrap_or_else(|error| error) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sqltext/ddl_effects.rs b/crates/sqlbuild-python/src/bindings/_helpers/sqltext/ddl_effects.rs index 3b48467554..baa50eca9a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sqltext/ddl_effects.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sqltext/ddl_effects.rs @@ -9,12 +9,11 @@ use sqlbuild_sqltext::ddl_effects::main::relation_name_key::relation_name_key as use sqlbuild_sqltext::ddl_effects::main::statement_metadata_effect::statement_metadata_effect as native_statement_metadata_effect; use sqlbuild_sqltext::ddl_effects::models::StatementMetadataEffect; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; -const EFFECT_MODEL: (&str, &str) = ( - "sqlbuild.adapter.relations.models", - "StatementMetadataEffect", -); +const EFFECT_MODEL: AdapterHost = AdapterHost::StatementMetadataEffect; static EFFECT_CLASS: PyOnceLock> = PyOnceLock::new(); @@ -24,9 +23,7 @@ static EFFECT_CLASS: PyOnceLock> = PyOnceLock::new(); fn statement_metadata_effect<'py>(py: Python<'py>, sql: &str) -> PyResult> { let effect: StatementMetadataEffect = py.detach(|| native_statement_metadata_effect(sql)); let class: &Bound<'py, PyAny> = EFFECT_CLASS - .get_or_try_init(py, || { - imported(py, EFFECT_MODEL.0, EFFECT_MODEL.1).map(Bound::unbind) - })? + .get_or_try_init(py, || host::entry(py, EFFECT_MODEL).map(Bound::unbind))? .bind(py); call_with( class, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sqltext/sql_references.rs b/crates/sqlbuild-python/src/bindings/_helpers/sqltext/sql_references.rs index c74782f322..e800067b7b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sqltext/sql_references.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sqltext/sql_references.rs @@ -1,6 +1,5 @@ //! Logical SQL references extracted natively under one adapter's lexical rules. -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class; use pyo3::prelude::{ Bound, PyAny, PyAnyMethods, PyErr, PyModule, PyModuleMethods, PyResult, Python, }; @@ -13,16 +12,12 @@ use sqlbuild_sqltext::sql_references::models::{ use sqlbuild_sqltext::sql_scan::models::LexicalSyntax; use crate::bindings::_helpers::boundary::panics::compiler_guard; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sqltext::lexical_syntax::LexicalSyntaxInput; -const NATIVE_RECORDS_MODULE: &str = "sqlbuild._native"; - -const COMPILE_MODELS_MODULE: &str = "sqlbuild._native"; const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; -const COMPILE_ERRORS_MODULE: &str = "sqlbuild._native"; -const REFERENCE_TYPES_MODULE: &str = "sqlbuild._native"; -const COLLECTOR_MODULE: &str = "sqlbuild._native"; const REFERENCE_CALL_SYNTAX_CODE: &str = "P012"; /// `(kind, name, package, call_argument_count)` for one reference. @@ -110,16 +105,13 @@ fn scanned<'py>( ReferenceExtraction::Extracted(scan) => scan, ReferenceExtraction::Failed(failure) => { let error: Bound<'py, PyAny> = - imported(py, COMPILE_ERRORS_MODULE, "CompileInputError")? - .call1((failure.message,))?; + registry::error(py, ErrorId::CompileInput)?.call1((failure.message,))?; return Err(PyErr::from_value(error)); } }; - let kind_type: Bound<'py, PyAny> = imported(py, REFERENCE_TYPES_MODULE, "SqlReferenceKind")?; - let reference_type: Bound<'py, PyAny> = - imported(py, COMPILE_MODELS_MODULE, "CompileSqlReference")?; - let invalid_type: Bound<'py, PyAny> = - imported(py, NATIVE_RECORDS_MODULE, "InvalidSqlReferenceCall")?; + let kind_type: Bound<'py, PyAny> = registry::class(py, RecordId::SqlReferenceKind)?; + let reference_type: Bound<'py, PyAny> = registry::class(py, RecordId::CompileSqlReference)?; + let invalid_type: Bound<'py, PyAny> = registry::class(py, RecordId::InvalidSqlReferenceCall)?; let references: Vec> = scan .references .into_iter() @@ -168,7 +160,7 @@ fn scan_sql_reference_calls<'py>( let py: Python<'py> = syntax.py(); let (references, invalid_calls) = scanned(py, sql, syntax)?; call_with( - &imported(py, NATIVE_RECORDS_MODULE, "SqlReferenceScan")?, + ®istry::class(py, RecordId::SqlReferenceScan)?, &[ ("references", PyTuple::new(py, references)?.into_any()), ("invalid_calls", PyTuple::new(py, invalid_calls)?.into_any()), @@ -185,9 +177,8 @@ pub(crate) fn extract_references<'py>( ) -> PyResult> { let py: Python<'py> = syntax.py(); let (references, invalid_calls) = scanned(py, sql, syntax)?; - let report: Bound<'py, PyAny> = imported(py, COLLECTOR_MODULE, "report_compile_diagnostic")?; - let diagnostic_type: Bound<'py, PyAny> = - imported(py, COMPILE_MODELS_MODULE, "CompilerDiagnostic")?; + let report: Bound<'py, PyAny> = registry::native(py, NativeAttr::ReportCompileDiagnostic)?; + let diagnostic_type: Bound<'py, PyAny> = registry::class(py, RecordId::CompilerDiagnostic)?; let types = py.import(COMPILE_TYPES_MODULE)?; for call in invalid_calls { let diagnostic: Bound<'py, PyAny> = call_with( @@ -195,11 +186,11 @@ pub(crate) fn extract_references<'py>( &[ ( "phase", - shape_class(types.as_any(), "DiagnosticPhase")?.getattr("COMPILE")?, + registry::class(types.py(), RecordId::DiagnosticPhase)?.getattr("COMPILE")?, ), ( "severity", - shape_class(types.as_any(), "DiagnosticSeverity")?.getattr("ERROR")?, + registry::class(types.py(), RecordId::DiagnosticSeverity)?.getattr("ERROR")?, ), ("code", text(py, REFERENCE_CALL_SYNTAX_CODE)?), ("message", call.getattr("message")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/state_rows/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/state_rows/constants.rs index 2d5507f85e..adf8039338 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/state_rows/constants.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/state_rows/constants.rs @@ -1,9 +1,5 @@ //! Python modules, names and messages of migration state rows. -pub(crate) const MIGRATION_MODELS: &str = "sqlbuild._native"; -pub(crate) const MIGRATION_TYPES: &str = "sqlbuild._native"; -pub(crate) const MIGRATION_EXCEPTIONS: &str = "sqlbuild._native"; -pub(crate) const SQL_VALUE_TYPES: &str = "sqlbuild._native"; pub(crate) const OLD_NAME_VIEW_CONFIG_KEY: &str = "old_name_view"; pub(crate) const MODEL_KIND: &str = "model"; pub(crate) const COLUMN_KIND: &str = "column"; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/state_rows/events.rs b/crates/sqlbuild-python/src/bindings/_helpers/state_rows/events.rs index dded2008a1..e79ff7e3d4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/state_rows/events.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/state_rows/events.rs @@ -16,11 +16,10 @@ use sqlbuild_state::events::main::old_name_view_event_id::old_name_view_event_id use sqlbuild_state::events::models::{EventRelation, StateCell, StateColumnType, StateValue}; use crate::bindings::_helpers::boundary::keyword_request::Request; -use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::state_rows::timestamps::{date_time_typed, presumed_utc}; -const SQL_VALUES_EXCEPTIONS: &str = "sqlbuild.sql_values.exceptions"; - fn emit_error(error: JsonEmitError) -> PyErr { PyValueError::new_err(format!("{error:?}")) } @@ -110,7 +109,7 @@ pub(crate) fn typed_insert_sql( /// `SqlValueRenderingError(message)` for an ill-typed value. fn rendering_error(py: Python<'_>) -> impl Fn(StateSqlError) -> PyErr + '_ { move |error: StateSqlError| { - let raised = imported(py, SQL_VALUES_EXCEPTIONS, "SqlValueRenderingError") + let raised = host::entry(py, ExecutorHost::SqlValueRenderingError) .and_then(|class| class.call1((error.0,))); match raised { Ok(exception) => PyErr::from_value(exception), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/state_rows/freshness.rs b/crates/sqlbuild-python/src/bindings/_helpers/state_rows/freshness.rs index 578d6b0c4e..df4e44520c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/state_rows/freshness.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/state_rows/freshness.rs @@ -19,18 +19,18 @@ use sqlbuild_state::freshness::types::TimestampParser; use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::cursor::values::{DATETIME_MODULE, local_date_time}; +use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::state_rows::timestamps::{ date_time_typed, in_utc, offset_aware, presumed_utc, }; -const NATIVE_RECORDS: &str = "sqlbuild._native"; -const EXCEPTIONS_MODULE: &str = "sqlbuild._native"; const TIMESTAMP_KIND: &str = "timestamp"; const INTEGER_KIND: &str = "integer"; const STRING_KIND: &str = "string"; -fn raised(py: Python<'_>, class: &str, message: String) -> PyErr { - match imported(py, EXCEPTIONS_MODULE, class).and_then(|class| class.call1((message,))) { +fn raised(py: Python<'_>, class: NativeAttr, message: String) -> PyErr { + match registry::native(py, class).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } @@ -39,9 +39,11 @@ fn raised(py: Python<'_>, class: &str, message: String) -> PyErr { fn python_error(py: Python<'_>, error: FreshnessError) -> PyErr { match error { FreshnessError::Observation(message) => { - raised(py, "SourceFreshnessObservationError", message) + raised(py, NativeAttr::SourceFreshnessObservationError, message) + } + FreshnessError::Input(message) => { + raised(py, NativeAttr::SourceFreshnessInputError, message) } - FreshnessError::Input(message) => raised(py, "SourceFreshnessInputError", message), FreshnessError::Overflow(message) => PyOverflowError::new_err(message), } } @@ -60,7 +62,11 @@ pub(crate) fn version_hash( } fn observation_error(py: Python<'_>, message: &str) -> PyErr { - raised(py, "SourceFreshnessObservationError", message.to_owned()) + raised( + py, + NativeAttr::SourceFreshnessObservationError, + message.to_owned(), + ) } /// A source freshness value normalized for stable state storage. @@ -107,7 +113,7 @@ pub(crate) fn normalize<'py>( } Err(raised( py, - "SourceFreshnessObservationError", + NativeAttr::SourceFreshnessObservationError, format!( "Unsupported source freshness value kind: {}", value_kind.str()? @@ -206,7 +212,7 @@ pub(crate) fn age_policy<'py>( if policy.is_none() { return Ok(None); } - let statuses: Bound<'py, PyAny> = imported(py, NATIVE_RECORDS, "SourceFreshnessAgeStatus")?; + let statuses: Bound<'py, PyAny> = registry::class(py, RecordId::SourceFreshnessAgeStatus)?; if !date_time_typed(data_version)? { return statuses.getattr("UNKNOWN").map(Some); } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/state_rows/migration_objects.rs b/crates/sqlbuild-python/src/bindings/_helpers/state_rows/migration_objects.rs index 5487870783..c96a3c8618 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/state_rows/migration_objects.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/state_rows/migration_objects.rs @@ -11,10 +11,10 @@ use sqlbuild_state::events::constants::{ }; use sqlbuild_state::events::models::EventOrder; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; -use crate::bindings::_helpers::state_rows::constants::{ - COLUMN_KIND, MIGRATION_EXCEPTIONS, MODEL_KIND, OLD_NAME_KIND, -}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::ErrorId; +use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::state_rows::constants::{COLUMN_KIND, MODEL_KIND, OLD_NAME_KIND}; use crate::bindings::_helpers::state_rows::models::{EventTable, EventTableLayout}; use crate::bindings::_helpers::state_rows::timestamps::presumed_utc; @@ -56,9 +56,7 @@ pub(crate) fn layout(kind: &str) -> PyResult { /// `MigrationStateError(message)`. pub(crate) fn state_error(py: Python<'_>, message: &str) -> PyErr { - match imported(py, MIGRATION_EXCEPTIONS, "MigrationStateError") - .and_then(|class| class.call1((message,))) - { + match registry::error(py, ErrorId::MigrationState).and_then(|class| class.call1((message,))) { Ok(error) => PyErr::from_value(error), Err(error) => error, } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/state_rows/migration_projections.rs b/crates/sqlbuild-python/src/bindings/_helpers/state_rows/migration_projections.rs index cb87db8e4c..0da6a9176b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/state_rows/migration_projections.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/state_rows/migration_projections.rs @@ -11,10 +11,10 @@ use sqlbuild_state::events::models::{ ColumnEventKey, EventOrder, EventRelation, MoveEventKey, OldNameFactKey, OldNameStep, }; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; -use crate::bindings::_helpers::state_rows::constants::{ - MIGRATION_MODELS, OLD_NAME_VIEW_CONFIG_KEY, -}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::state_rows::constants::OLD_NAME_VIEW_CONFIG_KEY; use crate::bindings::_helpers::state_rows::events::relation as event_relation; use crate::bindings::_helpers::state_rows::migration_objects::event_order; @@ -108,7 +108,7 @@ pub(crate) fn old_name_views<'py>( }) }) .collect::>()?; - let history = imported(py, MIGRATION_MODELS, "OldNameViewHistory")?; + let history = registry::class(py, RecordId::OldNameViewHistory)?; let fact_at = |index: Option| chosen(py, &facts, index); let histories: Vec> = old_name_histories(&move_orders, &fact_keys) .into_iter() diff --git a/crates/sqlbuild-python/src/bindings/_helpers/state_rows/migration_rows.rs b/crates/sqlbuild-python/src/bindings/_helpers/state_rows/migration_rows.rs index 737dc4d356..c0af2ecbc0 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/state_rows/migration_rows.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/state_rows/migration_rows.rs @@ -1,6 +1,5 @@ //! Create, read and append SQL of migration state tables, and stored rows decoded to events. -use crate::bindings::_helpers::compiler_records::shape_classes::shape_class_at; use pyo3::exceptions::{PyKeyError, PyValueError}; use pyo3::prelude::{ Bound, PyAny, PyAnyMethods, PyErr, PyModule, PyModuleMethods, PyResult, Python, @@ -19,9 +18,8 @@ use sqlbuild_state::events::main::readable_columns::readable_columns; use crate::bindings::_helpers::boundary::keyword_request::Request; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; use crate::bindings::_helpers::cursor::values::DATETIME_MODULE; -use crate::bindings::_helpers::state_rows::constants::{ - MIGRATION_MODELS, MIGRATION_TYPES, SQL_VALUE_TYPES, -}; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::state_rows::events::typed_insert_sql; use crate::bindings::_helpers::state_rows::migration_objects::{ layout, qualified_table, state_error, @@ -42,7 +40,7 @@ fn create_sql<'py>(request: Option<&Bound<'py, PyDict>>) -> PyResult = imported(py, SQL_VALUE_TYPES, "StateSqlValueType")?; + let value_types: Bound<'py, PyAny> = registry::class(py, RecordId::StateSqlValueType)?; let column_types: Bound<'py, PyDict> = PyDict::new(py); for column in layout.columns { let member: &str = if TIMESTAMP_COLUMNS.contains(column) { @@ -224,7 +222,7 @@ impl<'py> StoredRow<'_, 'py> { fn relation(&self, py: Python<'py>, prefix: &str) -> PyResult> { call_with( - &imported(py, MIGRATION_MODELS, "MigrationRelation")?, + ®istry::class(py, RecordId::MigrationRelation)?, &[ ( "database", @@ -255,15 +253,15 @@ fn timestamp<'py>(raw: &Bound<'py, PyAny>) -> PyResult> { fn enumerated<'py>( py: Python<'py>, - class: &str, + class: RecordId, value: Bound<'py, PyAny>, ) -> PyResult> { - shape_class_at(py, MIGRATION_TYPES, class)?.call1((value,)) + registry::class(py, class)?.call1((value,)) } fn known_member<'py>( py: Python<'py>, - class: &str, + class: RecordId, value: Bound<'py, PyAny>, ) -> PyResult>> { match enumerated(py, class, value) { @@ -282,7 +280,7 @@ fn model_event<'py>(py: Python<'py>, row: &StoredRow<'_, 'py>) -> PyResult(py: Python<'py>, row: &StoredRow<'_, 'py>) -> PyResult(py: Python<'py>, row: &StoredRow<'_, 'py>) -> PyResult(py: Python<'py>, row: &StoredRow<'_, 'py>) -> PyResult> { call_with( - &imported(py, MIGRATION_MODELS, "ColumnMigrationEvent")?, + ®istry::class(py, RecordId::ColumnMigrationEvent)?, &[ ("event_id", row.text("event_id")?), ("target_name", row.nullable(py, "target_name")?), @@ -317,11 +315,11 @@ fn column_event<'py>(py: Python<'py>, row: &StoredRow<'_, 'py>) -> PyResult( Some(value) => value.str()?.into_any(), None => PyString::new(py, "None").into_any(), }; - let Some(event_type) = known_member(py, "OldNameViewEventType", raw_type)? else { + let Some(event_type) = known_member(py, RecordId::OldNameViewEventType, raw_type)? else { return Ok(None); }; let drop_reason: Option> = match row.optional_text("drop_reason")? { - Some(raw) => known_member(py, "OldNameViewDropReason", raw)?, + Some(raw) => known_member(py, RecordId::OldNameViewDropReason, raw)?, None => None, }; let none = || py.None().into_bound(py); @@ -380,11 +378,7 @@ fn old_name_event<'py>( ("grants_copied", grants), ("view_sql", row.nullable(py, "view_sql")?), ]; - call_with( - &imported(py, MIGRATION_MODELS, "OldNameViewEvent")?, - &fields, - ) - .map(Some) + call_with(®istry::class(py, RecordId::OldNameViewEvent)?, &fields).map(Some) } /// Stored rows of the selected `columns` as events; rows this version cannot read are skipped. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/state_rows/state_tables.rs b/crates/sqlbuild-python/src/bindings/_helpers/state_rows/state_tables.rs index 3cc4994e3a..90a55d550b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/state_rows/state_tables.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/state_rows/state_tables.rs @@ -15,11 +15,11 @@ use sqlbuild_state::state_tables::main::values_insert_sql::values_insert_sql; use sqlbuild_state::state_tables::models::TableColumn; use crate::bindings::_helpers::boundary::keyword_request::Request; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::contracts::state_literals::render_state_sql_literal; -use crate::bindings::_helpers::freshness::support::ADAPTER_EXCEPTIONS; +use crate::bindings::_helpers::native_host::adapter::AdapterHost; +use crate::bindings::_helpers::native_host::host; -const ADAPTER_TYPES: &str = "sqlbuild.adapter.contract.types"; const MISSING_SCHEMA_MESSAGE: &str = "source freshness table requires a target schema"; /// Each state value type a state table may declare and the framework type that renders it. const DECLARED_TYPES: [(&str, &str); 3] = [ @@ -39,7 +39,7 @@ pub(crate) fn state_table_create_sql<'py>( render_framework_type: &Bound<'py, PyAny>, ) -> PyResult { let py: Python<'py> = render_framework_type.py(); - let framework_types: Bound<'py, PyAny> = imported(py, ADAPTER_TYPES, "FrameworkType")?; + let framework_types: Bound<'py, PyAny> = host::entry(py, AdapterHost::FrameworkType)?; let mut rendered: Vec<(&str, String)> = Vec::with_capacity(DECLARED_TYPES.len()); for (declared, framework) in DECLARED_TYPES { let text: String = render_framework_type @@ -82,7 +82,7 @@ fn render_create_sql(request: Option<&Bound<'_, PyDict>>) -> PyResult { } fn adapter_user_error(py: Python<'_>, message: &str) -> PyErr { - match imported(py, ADAPTER_EXCEPTIONS, "AdapterUserError").and_then(|class| { + match host::entry(py, AdapterHost::AdapterUserError).and_then(|class| { call_with( &class, &[("message", PyString::new(py, message).into_any())], diff --git a/crates/sqlbuild-python/src/bindings/_helpers/type_system/equality.rs b/crates/sqlbuild-python/src/bindings/_helpers/type_system/equality.rs index 3af3c77ea6..cc2553b41a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/type_system/equality.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/type_system/equality.rs @@ -9,10 +9,11 @@ use sqlbuild_analysis::type_system::main::normalize_type::normalize_type; use sqlbuild_analysis::type_system::models::{NormalizedType, TypeNormalization}; use crate::bindings::_helpers::boundary::panics::compiler_guard; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::native_host::executor::ExecutorHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::type_system::normalization::normalization_error; -const DEBUG_EVENT_MODULE: &str = "sqlbuild.diagnostics.main.log_debug_event"; const TYPE_NORMALIZATION_LOGGER: &str = "sqlbuild.adapter"; const TYPE_PARSE_FALLBACK_MESSAGE: &str = "type normalization polyglot parse failed; falling back"; const DEFAULT_DIALECT: &str = "generic"; @@ -69,7 +70,7 @@ fn log_parse_fallback( ) -> PyResult<()> { let py: Python<'_> = dialect.py(); call_with( - &imported(py, DEBUG_EVENT_MODULE, "log_debug_event")?, + &host::entry(py, ExecutorHost::LogDebugEvent)?, &[ ( "logger", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/workspace_commands/commands.rs b/crates/sqlbuild-python/src/bindings/_helpers/workspace_commands/commands.rs index bae5bed561..c40910c9e4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/workspace_commands/commands.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/workspace_commands/commands.rs @@ -20,12 +20,14 @@ use sqlbuild_config::workspace::models::{PlaygroundRequest, SkillInstallRequest} use sqlbuild_core::cli_text::models::CliStyle; use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported}; +use crate::bindings::_helpers::native_host::facade::FacadeHost; +use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::registry; const SKILLS_DIRECTORY: &str = ".agents/skills/sqlbuild"; const TEMPLATES_DIRECTORY: &str = "playground_templates"; -const EXCEPTIONS_MODULE: &str = "sqlbuild._native"; -const OUTPUT_MODELS: &str = "sqlbuild._native"; const SKILLS_OPTIONS: (&[&str], &[&str]) = (&["project_dir"], &["global_install", "targets", "force"]); const UPDATE_OPTIONS: (&[&str], &[&str]) = ( @@ -45,8 +47,8 @@ fn raised(py: Python<'_>, error: WorkspaceError) -> PyErr { let keywords: Bound<'_, PyDict> = PyDict::new(py); keywords.set_item("code", code)?; keywords.set_item("help", help)?; - let instance: Bound<'_, PyAny> = imported(py, EXCEPTIONS_MODULE, "CliUserError")? - .call((message,), Some(&keywords))?; + let instance: Bound<'_, PyAny> = + registry::error(py, ErrorId::CliUser)?.call((message,), Some(&keywords))?; Ok(PyErr::from_value(instance)) }; created().unwrap_or_else(|failure| failure) @@ -63,7 +65,7 @@ fn raised(py: Python<'_>, error: WorkspaceError) -> PyErr { /// The installed `sqlbuild` package directory. fn package_dir(py: Python<'_>) -> PyResult { - let file: PathBuf = py.import("sqlbuild")?.getattr("__file__")?.extract()?; + let file: PathBuf = host::entry(py, FacadeHost::PackageFile)?.extract()?; Ok(file.parent().map(Path::to_path_buf).unwrap_or_default()) } @@ -117,7 +119,7 @@ fn update_sqlbuild_skills<'py>( let written: Vec = install(py, &request)?; let keywords: Bound<'py, PyDict> = PyDict::new(py); keywords.set_item("written_paths", PyTuple::new(py, written)?)?; - imported(py, OUTPUT_MODELS, "SkillUpdateResult")?.call((), Some(&keywords)) + registry::class(py, RecordId::SkillUpdateResult)?.call((), Some(&keywords)) } /// Check or refresh generated project skills without changing command success. @@ -128,7 +130,7 @@ fn maintain_sqlbuild_skills(py: Python<'_>, project_dir: PathBuf) -> PyResult = PyDict::new(py); keywords.set_item("message", message)?; - imported(py, OUTPUT_MODELS, "SkillMaintenanceResult")?.call((), Some(&keywords)) + registry::class(py, RecordId::SkillMaintenanceResult)?.call((), Some(&keywords)) } /// Return every packaged skill file keyed by skill-relative path, with generated markers. diff --git a/crates/sqlbuild-python/src/bindings/main/register.rs b/crates/sqlbuild-python/src/bindings/main/register.rs index d47d397c40..1643c96c6d 100644 --- a/crates/sqlbuild-python/src/bindings/main/register.rs +++ b/crates/sqlbuild-python/src/bindings/main/register.rs @@ -8,15 +8,17 @@ use crate::bindings::_helpers::{ compile_attachment, compile_command, compile_driver, compile_outputs, compiled_project, contracts, cursor, dag_artifact, diagnostics, discovery, exported_constants, fingerprints, frontier, graph, lifecycle_events, lineage, macro_bridge, manifest, microbatches, model_config, - plan_output, planner_registration, project_assembly, records, refactor, references, render, - rules, scopes, semantic_checks, semantic_shapes, sql_test_glue, sqltext, state_rows, - type_system, + native_host, plan_output, planner_registration, project_assembly, records, refactor, + references, render, rules, scopes, semantic_checks, semantic_shapes, sql_test_glue, sqltext, + state_rows, type_system, }; use crate::bindings::models; #[pymodule] pub fn _native(module: &Bound<'_, PyModule>) -> PyResult<()> { records::registration::register(module)?; + records::registry::verify_tables()?; + native_host::registration::register(module)?; rules::registration::register(module)?; cli::registration::register(module)?; analysis::normalization::register(module)?; @@ -33,7 +35,7 @@ pub fn _native(module: &Bound<'_, PyModule>) -> PyResult<()> { register_analysis_stages(module)?; module.add("API_VERSION", API_VERSION)?; module.add("BUILD_IDENTITY", NATIVE_BUILD_IDENTITY)?; - Ok(()) + records::registry::verify_attributes(module) } fn register_project_stages(module: &Bound<'_, PyModule>) -> PyResult<()> { diff --git a/fensu.toml b/fensu.toml index db6188ab05..b4a5217090 100644 --- a/fensu.toml +++ b/fensu.toml @@ -296,6 +296,11 @@ rule = "FFR402" path = "src/sqlbuild/rules/__init__.py" reason = "These imports provide the documented sqlbuild.rules package-root authoring API." +[[targets.python.rule_exceptions]] +rule = "FFR307" +path = "src/sqlbuild/_native_host.py" +reason = "The native host boundary: the one module native code imports by path, naming every staying-Python entry point native code may call." + [[targets.python.rule_exceptions]] rule = "FFR307" path = "src/sqlbuild/rules/testing.py" diff --git a/python-boundary.toml b/python-boundary.toml index 4b1df2ee41..1f018ef03d 100644 --- a/python-boundary.toml +++ b/python-boundary.toml @@ -552,6 +552,11 @@ path = "compiler/sql_analysis/_helpers/polyglot.py" rule = 4 note = "Wraps the Polyglot Python package with complexity guards." +[[stays]] +path = "_native_host.py" +rule = 1 +note = "The one module native code imports by path: a lazy name table of the executor, adapter, user-code, third-party and facade entry points the native host table (crates/sqlbuild-python native_host) reaches." + [[stays]] path = "assets.py" rule = 3 diff --git a/scripts/native_module_paths/_helpers/scan.py b/scripts/native_module_paths/_helpers/scan.py index e830a6fc39..75c0ab66f5 100644 --- a/scripts/native_module_paths/_helpers/scan.py +++ b/scripts/native_module_paths/_helpers/scan.py @@ -5,6 +5,8 @@ from scripts.native_module_paths.constants import ( DOTTED_PATH, DYNAMIC_MODULE_PATHS, + HOST_ENTRY, + HOST_MODULE, IMPORT_GETATTR_CHAIN, IMPORT_HELPER_CALL, LITERAL_MODULE_ATTRIBUTE_PAIR, @@ -16,7 +18,7 @@ def scan_native_source(*, source: str, label: str) -> NativeModulePathScan: - """Return the module paths, module attributes and dynamic module paths in one Rust file.""" + """Return the module paths, attributes, host entries and dynamic module paths in one file.""" constants: dict[str, str] = { match.group(1): match.group(2) for match in STRING_CONSTANT.finditer(source) } @@ -51,6 +53,14 @@ def scan_native_source(*, source: str, label: str) -> NativeModulePathScan: attribute=match.group(2), ) ) + references.extend( + ModulePathReference( + location=_location(source=source, label=label, offset=match.start()), + path=HOST_MODULE, + attribute=match.group(1), + ) + for match in HOST_ENTRY.finditer(source) + ) for pattern in DYNAMIC_MODULE_PATHS: violations.extend( f"{_location(source=source, label=label, offset=match.start())}: module path is " diff --git a/scripts/native_module_paths/constants.py b/scripts/native_module_paths/constants.py index 7cf304ae84..192e7b5ecf 100644 --- a/scripts/native_module_paths/constants.py +++ b/scripts/native_module_paths/constants.py @@ -32,3 +32,5 @@ ), re.compile(r"\b(?:format|concat)!\(\s*\"sqlbuild\."), ) +HOST_MODULE: str = "sqlbuild._native_host" +HOST_ENTRY: re.Pattern[str] = re.compile(r"\bHostEntry\s*\{\s*name:\s*" + ATTRIBUTE_NAME) diff --git a/src/sqlbuild/_native.pyi b/src/sqlbuild/_native.pyi index 5bfbae85ff..378b4a7620 100644 --- a/src/sqlbuild/_native.pyi +++ b/src/sqlbuild/_native.pyi @@ -168,6 +168,7 @@ from sqlbuild.runtime.output_capture.models import ( from sqlbuild.runtime.output_capture.types import CommandOutputStream BUILD_IDENTITY: str +HOST_ENTRIES: tuple[tuple[str, str, str], ...] MICROBATCH_START_SENTINEL: str MICROBATCH_END_SENTINEL: str MIGRATION_FINGERPRINT_METADATA_KEY: str diff --git a/src/sqlbuild/_native_host.py b/src/sqlbuild/_native_host.py new file mode 100644 index 0000000000..3e22a919a5 --- /dev/null +++ b/src/sqlbuild/_native_host.py @@ -0,0 +1,733 @@ +"""The only module native code imports by path: staying-Python entry points by name.""" + +from __future__ import annotations + +import importlib + +_TARGETS: dict[str, tuple[str, str]] = { + "BaseAdapter": ("sqlbuild.adapter.contract.classes.base_adapter", "BaseAdapter"), + "AdapterUserError": ("sqlbuild.adapter.contract.exceptions", "AdapterUserError"), + "ColumnInfo": ("sqlbuild.adapter.contract.models", "ColumnInfo"), + "ExpressionInferenceProfile": ( + "sqlbuild.adapter.contract.models", + "ExpressionInferenceProfile", + ), + "RelationInfo": ("sqlbuild.adapter.contract.models", "RelationInfo"), + "RelationLookup": ("sqlbuild.adapter.contract.models", "RelationLookup"), + "RetentionRequest": ("sqlbuild.adapter.contract.models", "RetentionRequest"), + "RowDiffSampling": ("sqlbuild.adapter.contract.models", "RowDiffSampling"), + "RowDiffTolerance": ("sqlbuild.adapter.contract.models", "RowDiffTolerance"), + "RowDiffTolerances": ("sqlbuild.adapter.contract.models", "RowDiffTolerances"), + "TableFreshnessMetadata": ("sqlbuild.adapter.contract.models", "TableFreshnessMetadata"), + "TableFreshnessRequest": ("sqlbuild.adapter.contract.models", "TableFreshnessRequest"), + "BuiltinAdapter": ("sqlbuild.adapter.contract.types", "BuiltinAdapter"), + "FrameworkType": ("sqlbuild.adapter.contract.types", "FrameworkType"), + "LifeCycleEventKind": ("sqlbuild.adapter.contract.types", "LifeCycleEventKind"), + "MigrationTransfer": ("sqlbuild.adapter.contract.types", "MigrationTransfer"), + "RetentionScope": ("sqlbuild.adapter.contract.types", "RetentionScope"), + "TableFreshnessStatus": ("sqlbuild.adapter.contract.types", "TableFreshnessStatus"), + "adapter_supports_session_warehouse": ( + "sqlbuild.adapter.discovery.main._session_warehouse_support", + "adapter_supports_session_warehouse", + ), + "builtin_adapter_classes": ( + "sqlbuild.adapter.discovery.main.builtins", + "builtin_adapter_classes", + ), + "discover_project_adapters": ( + "sqlbuild.adapter.discovery.main.project_adapters", + "discover_project_adapters", + ), + "resolve_adapter": ("sqlbuild.adapter.discovery.main.resolve_adapter", "resolve_adapter"), + "get_columns_for_inspection": ( + "sqlbuild.adapter.relations.main._get_columns_for_inspection", + "get_columns_for_inspection", + ), + "list_relations_for_inspection": ( + "sqlbuild.adapter.relations.main._list_relations_for_inspection", + "list_relations_for_inspection", + ), + "active_inspection_catalog": ( + "sqlbuild.adapter.relations.main.active_inspection_catalog", + "active_inspection_catalog", + ), + "inspection_query_sink": ( + "sqlbuild.adapter.relations.main.inspection_query_sink", + "inspection_query_sink", + ), + "open_inspection_catalog": ( + "sqlbuild.adapter.relations.main.open_inspection_catalog", + "open_inspection_catalog", + ), + "record_inspection_query": ( + "sqlbuild.adapter.relations.main.record_inspection_query", + "record_inspection_query", + ), + "build_relation_lookup": ( + "sqlbuild.adapter.relations.main.relation_lookup", + "build_relation_lookup", + ), + "resolve_relation_location_qualified_name": ( + "sqlbuild.adapter.relations.main.resolve_relation_location_qualified_name", + "resolve_relation_location_qualified_name", + ), + "run_bounded_inspections": ( + "sqlbuild.adapter.relations.main.run_bounded_inspections", + "run_bounded_inspections", + ), + "run_recorded_inspection_query": ( + "sqlbuild.adapter.relations.main.run_recorded_inspection_query", + "run_recorded_inspection_query", + ), + "InspectionQueryRecord": ("sqlbuild.adapter.relations.models", "InspectionQueryRecord"), + "StatementMetadataEffect": ("sqlbuild.adapter.relations.models", "StatementMetadataEffect"), + "conditional_result_nullability": ( + "sqlbuild.adapter.type_system.main.conditional_result_nullability", + "conditional_result_nullability", + ), + "first_arg_nullability": ( + "sqlbuild.adapter.type_system.main.first_arg_nullability", + "first_arg_nullability", + ), + "normalize_relation_type": ( + "sqlbuild.adapter.type_system.main.normalize_relation_type", + "normalize_relation_type", + ), + "normalize_type": ("sqlbuild.adapter.type_system.main.normalize_type", "normalize_type"), + "safe_cast_nullability": ( + "sqlbuild.adapter.type_system.main.safe_cast_nullability", + "safe_cast_nullability", + ), + "python_resource_rows": ( + "sqlbuild.compiler.compile._helpers.diagnostics.python_resources", + "python_resource_rows", + ), + "target_context_with_macro_call_store": ( + "sqlbuild.compiler.compile._helpers.macro_bridge.call_store", + "target_context_with_macro_call_store", + ), + "inventory_project_macros": ( + "sqlbuild.compiler.compile._helpers.render.user_macros", + "inventory_project_macros", + ), + "load_project_macros": ( + "sqlbuild.compiler.compile._helpers.render.user_macros", + "load_project_macros", + ), + "macro_invocation_context": ( + "sqlbuild.compiler.compile._helpers.render.user_macros", + "macro_invocation_context", + ), + "discover_command_output_sink_functions": ( + "sqlbuild.compiler.discovery._helpers.filesystem.command_output_sinks", + "discover_command_output_sink_functions", + ), + "import_python_root_module": ( + "sqlbuild.compiler.discovery._helpers.filesystem.project_modules", + "import_python_root_module", + ), + "install_python_root_package": ( + "sqlbuild.compiler.discovery._helpers.filesystem.project_modules", + "install_python_root_package", + ), + "load_materialize_function": ( + "sqlbuild.compiler.discovery._helpers.filesystem.project_modules", + "load_materialize_function", + ), + "load_provider_module": ( + "sqlbuild.compiler.discovery._helpers.filesystem.project_modules", + "load_provider_module", + ), + "load_python_node_module": ( + "sqlbuild.compiler.discovery._helpers.filesystem.project_modules", + "load_python_node_module", + ), + "load_sink_module": ( + "sqlbuild.compiler.discovery._helpers.filesystem.project_modules", + "load_sink_module", + ), + "module_functions": ( + "sqlbuild.compiler.discovery._helpers.filesystem.project_modules", + "module_functions", + ), + "module_provider_classes": ( + "sqlbuild.compiler.discovery._helpers.filesystem.project_modules", + "module_provider_classes", + ), + "parameter_annotations": ( + "sqlbuild.compiler.discovery._helpers.filesystem.project_modules", + "parameter_annotations", + ), + "provider_usages": ( + "sqlbuild.compiler.discovery._helpers.filesystem.project_modules", + "provider_usages", + ), + "requires_arguments": ( + "sqlbuild.compiler.discovery._helpers.filesystem.project_modules", + "requires_arguments", + ), + "restore_python_root_modules": ( + "sqlbuild.compiler.discovery._helpers.filesystem.project_modules", + "restore_python_root_modules", + ), + "source_line": ( + "sqlbuild.compiler.discovery._helpers.filesystem.project_modules", + "source_line", + ), + "validate_event_exporter_signature": ( + "sqlbuild.compiler.discovery._helpers.filesystem.project_modules", + "validate_event_exporter_signature", + ), + "validate_discovered_inputs": ( + "sqlbuild.compiler.discovery._helpers.validation.discovery", + "validate_discovered_inputs", + ), + "discover_runtime_extensions": ( + "sqlbuild.compiler.discovery.main.runtime_extensions", + "discover_runtime_extensions", + ), + "run_with_macro_bridge": ( + "sqlbuild.compiler.macro_bridge.classes.macro_bridge", + "run_with_macro_bridge", + ), + "macro_store_module_paths": ( + "sqlbuild.compiler.macro_bridge.main._macro_store_module_paths", + "macro_store_module_paths", + ), + "active_macro_bridge": ( + "sqlbuild.compiler.macro_bridge.main.active_macro_bridge", + "active_macro_bridge", + ), + "load_custom_materializations": ( + "sqlbuild.compiler.pipeline._helpers.materializations", + "load_custom_materializations", + ), + "python_node_dependency_key": ( + "sqlbuild.compiler.python_nodes._helpers.user_inventory", + "python_node_dependency_key", + ), + "python_node_identity": ( + "sqlbuild.compiler.python_nodes._helpers.user_inventory", + "python_node_identity", + ), + "build_hook_identities": ( + "sqlbuild.compiler.python_nodes.main._hook_identities", + "build_hook_identities", + ), + "PythonNodeKind": ("sqlbuild.compiler.python_nodes.types", "PythonNodeKind"), + "PythonNodeStatus": ("sqlbuild.compiler.python_nodes.types", "PythonNodeStatus"), + "ProviderInputError": ("sqlbuild.provider.exceptions", "ProviderInputError"), + "build_provider_session": ("sqlbuild.provider.main.session", "build_provider_session"), + "read_asset_definition": ( + "sqlbuild.python_nodes.main.read_asset_definition", + "read_asset_definition", + ), + "read_audit_factory_definition": ( + "sqlbuild.python_nodes.main.read_audit_factory_definition", + "read_audit_factory_definition", + ), + "read_check_definition": ( + "sqlbuild.python_nodes.main.read_check_definition", + "read_check_definition", + ), + "read_factory_definition": ( + "sqlbuild.python_nodes.main.read_factory_definition", + "read_factory_definition", + ), + "read_hook_definition": ( + "sqlbuild.python_nodes.main.read_hook_definition", + "read_hook_definition", + ), + "read_loader_definition": ( + "sqlbuild.python_nodes.main.read_loader_definition", + "read_loader_definition", + ), + "read_task_definition": ( + "sqlbuild.python_nodes.main.read_task_definition", + "read_task_definition", + ), + "AuditCase": ("sqlbuild.python_nodes.models", "AuditCase"), + "RELATION_RENDERER": ("sqlbuild.python_nodes.models", "RELATION_RENDERER"), + "SqlResourceRef": ("sqlbuild.python_nodes.models", "SqlResourceRef"), + "PythonCheckSeverity": ("sqlbuild.python_nodes.types", "PythonCheckSeverity"), + "SqlResourceRefKind": ("sqlbuild.python_nodes.types", "SqlResourceRefKind"), + "load_custom_rules": ( + "sqlbuild.rule_engine._helpers.engine._custom_catalogue", + "load_custom_rules", + ), + "custom_rule_payloads": ( + "sqlbuild.rule_engine._helpers.engine.custom_payloads", + "custom_rule_payloads", + ), + "fact_outcome_digest": ( + "sqlbuild.rule_engine._helpers.engine.fact_replay", + "fact_outcome_digest", + ), + "full_fact_keys": ("sqlbuild.rule_engine._helpers.engine.fact_replay", "full_fact_keys"), + "is_tree_fact": ("sqlbuild.rule_engine._helpers.engine.fact_replay", "is_tree_fact"), + "shared_fact_encoder": ( + "sqlbuild.rule_engine._helpers.engine.fact_replay", + "shared_fact_encoder", + ), + "build_rule_fact_views": ("sqlbuild.rule_engine.classes.rule_context", "build_rule_fact_views"), + "EventExporterInputError": ( + "sqlbuild.runtime.event_exporting.exceptions", + "EventExporterInputError", + ), + "EventExporterStateError": ( + "sqlbuild.runtime.event_exporting.exceptions", + "EventExporterStateError", + ), + "invoke_lifecycle_event_sink": ( + "sqlbuild.runtime.event_exporting.main._invoke_lifecycle_event_sink", + "invoke_lifecycle_event_sink", + ), + "run_command_output_sinks": ( + "sqlbuild.runtime.event_exporting.main._run_command_output_sinks", + "run_command_output_sinks", + ), + "get_lifecycle_event_sink_definition": ( + "sqlbuild.runtime.event_exporting.main.get_lifecycle_event_sink_definition", + "get_lifecycle_event_sink_definition", + ), + "CostResourceContext": ("sqlbuild.cost.models", "CostResourceContext"), + "RunCostSummary": ("sqlbuild.cost.models", "RunCostSummary"), + "CostCapability": ("sqlbuild.cost.types", "CostCapability"), + "CostStatus": ("sqlbuild.cost.types", "CostStatus"), + "BoundSentinel": ("sqlbuild.cursor_algebra.types", "BoundSentinel"), + "read_process_resources": ( + "sqlbuild.diagnostics._helpers.process_resources", + "read_process_resources", + ), + "SQL_PRIVATE_RECORD_MARKER": ("sqlbuild.diagnostics.constants", "SQL_PRIVATE_RECORD_MARKER"), + "diagnostics_context": ("sqlbuild.diagnostics.main.diagnostics_context", "diagnostics_context"), + "log_debug_event": ("sqlbuild.diagnostics.main.log_debug_event", "log_debug_event"), + "log_sql": ("sqlbuild.diagnostics.main.log_sql", "log_sql"), + "process_resource_reporting": ( + "sqlbuild.diagnostics.main.process_resource_reporting", + "process_resource_reporting", + ), + "ProcessResourceUsage": ("sqlbuild.diagnostics.models", "ProcessResourceUsage"), + "ExecutorInputError": ("sqlbuild.errors.contracts.exceptions", "ExecutorInputError"), + "current_audit_result_projection": ( + "sqlbuild.executor.auditing.main._current_audit_result_projection", + "current_audit_result_projection", + ), + "AuditExecutionResult": ("sqlbuild.executor.auditing.models", "AuditExecutionResult"), + "BuildCallbacks": ("sqlbuild.executor.build.models", "BuildCallbacks"), + "BuildCustomizations": ("sqlbuild.executor.build.models", "BuildCustomizations"), + "BuildExecutionResult": ("sqlbuild.executor.build.models", "BuildExecutionResult"), + "BuildInitialState": ("sqlbuild.executor.build.models", "BuildInitialState"), + "BuildRuntimeParams": ("sqlbuild.executor.build.models", "BuildRuntimeParams"), + "FunctionExecutionResult": ("sqlbuild.executor.build.models", "FunctionExecutionResult"), + "SeedExecutionResult": ("sqlbuild.executor.build.models", "SeedExecutionResult"), + "BuildStatus": ("sqlbuild.executor.build.types", "BuildStatus"), + "build_destination_transient_models": ( + "sqlbuild.executor.clone.main._build_destination_transient_models", + "build_destination_transient_models", + ), + "build_destination_retention_requests": ( + "sqlbuild.executor.clone.main._build_retention_requests", + "build_destination_retention_requests", + ), + "copy_clone_fingerprints": ( + "sqlbuild.executor.clone.main._fingerprinting", + "copy_clone_fingerprints", + ), + "run_prephase_clone_stream": ( + "sqlbuild.executor.clone.main._run_prephase_clone_stream", + "run_prephase_clone_stream", + ), + "execute_clone": ("sqlbuild.executor.clone.main.execute", "execute_clone"), + "CloneExecutionInput": ("sqlbuild.executor.clone.models", "CloneExecutionInput"), + "CloneItemResult": ("sqlbuild.executor.clone.models", "CloneItemResult"), + "CloneSourceEntries": ("sqlbuild.executor.clone.models", "CloneSourceEntries"), + "CloneStatus": ("sqlbuild.executor.clone.types", "CloneStatus"), + "QueryDiffArtifactLifecycle": ( + "sqlbuild.executor.diff.classes.query_artifact_lifecycle", + "QueryDiffArtifactLifecycle", + ), + "FullDiffSizeGuardError": ("sqlbuild.executor.diff.exceptions", "FullDiffSizeGuardError"), + "execute_query_diff": ("sqlbuild.executor.diff.main._execute_query", "execute_query_diff"), + "execute_diff": ("sqlbuild.executor.diff.main.execute", "execute_diff"), + "DiffExecutionOptions": ("sqlbuild.executor.diff.models", "DiffExecutionOptions"), + "DiffExecutionResult": ("sqlbuild.executor.diff.models", "DiffExecutionResult"), + "FullDiffSizeLimits": ("sqlbuild.executor.diff.models", "FullDiffSizeLimits"), + "RowDiffSamplingOverride": ("sqlbuild.executor.diff.models", "RowDiffSamplingOverride"), + "execute_janitor_plan": ("sqlbuild.executor.janitor.main.execute", "execute_janitor_plan"), + "JanitorArchiveCandidate": ("sqlbuild.executor.janitor.models", "JanitorArchiveCandidate"), + "JanitorArchivedRelation": ("sqlbuild.executor.janitor.models", "JanitorArchivedRelation"), + "JanitorBlockedSchema": ("sqlbuild.executor.janitor.models", "JanitorBlockedSchema"), + "JanitorDeleteCandidate": ("sqlbuild.executor.janitor.models", "JanitorDeleteCandidate"), + "JanitorDirectStatePruneCandidate": ( + "sqlbuild.executor.janitor.models", + "JanitorDirectStatePruneCandidate", + ), + "JanitorOldNameView": ("sqlbuild.executor.janitor.models", "JanitorOldNameView"), + "JanitorOldNameViewPlanning": ( + "sqlbuild.executor.janitor.models", + "JanitorOldNameViewPlanning", + ), + "JanitorPlan": ("sqlbuild.executor.janitor.models", "JanitorPlan"), + "JanitorQueryDiffArtifactCandidate": ( + "sqlbuild.executor.janitor.models", + "JanitorQueryDiffArtifactCandidate", + ), + "JanitorRelationKey": ("sqlbuild.executor.janitor.models", "JanitorRelationKey"), + "JanitorSkippedRelation": ("sqlbuild.executor.janitor.models", "JanitorSkippedRelation"), + "JanitorSkippedSchema": ("sqlbuild.executor.janitor.models", "JanitorSkippedSchema"), + "run_load_pipeline": ("sqlbuild.executor.load.main.run", "run_load_pipeline"), + "LoadCallbacks": ("sqlbuild.executor.load.models", "LoadCallbacks"), + "LoadExecutionResult": ("sqlbuild.executor.load.models", "LoadExecutionResult"), + "LoadRuntimeParams": ("sqlbuild.executor.load.models", "LoadRuntimeParams"), + "migration_relation_for_location": ( + "sqlbuild.executor.migrations._helpers.relation_for_location", + "migration_relation_for_location", + ), + "is_compatibility_view": ( + "sqlbuild.executor.old_name_views._helpers.janitor", + "is_compatibility_view", + ), + "AuditExecutionError": ("sqlbuild.executor.pipeline.exceptions", "AuditExecutionError"), + "AuditPipelineCallbacks": ("sqlbuild.executor.pipeline.main.run", "AuditPipelineCallbacks"), + "run_audit_pipeline_with_callbacks": ( + "sqlbuild.executor.pipeline.main.run", + "run_audit_pipeline_with_callbacks", + ), + "run_build_pipeline": ("sqlbuild.executor.pipeline.main.run", "run_build_pipeline"), + "run_scenario_capture_pipeline": ( + "sqlbuild.executor.pipeline.main.run", + "run_scenario_capture_pipeline", + ), + "run_scenario_local_test_pipeline": ( + "sqlbuild.executor.pipeline.main.run", + "run_scenario_local_test_pipeline", + ), + "run_scenario_test_pipeline": ( + "sqlbuild.executor.pipeline.main.run", + "run_scenario_test_pipeline", + ), + "run_seed_pipeline": ("sqlbuild.executor.pipeline.main.run", "run_seed_pipeline"), + "run_test_pipeline": ("sqlbuild.executor.pipeline.main.run", "run_test_pipeline"), + "select_scenario_snapshot_capture_candidates": ( + "sqlbuild.executor.pipeline.main.run", + "select_scenario_snapshot_capture_candidates", + ), + "TestPipelineCallbacks": ("sqlbuild.executor.pipeline.models", "TestPipelineCallbacks"), + "execute_python_checks": ( + "sqlbuild.executor.python_nodes.main._checks", + "execute_python_checks", + ), + "execute_ingress_python_loader_nodes": ( + "sqlbuild.executor.python_nodes.main._ingress", + "execute_ingress_python_loader_nodes", + ), + "run_ingress_python_loader_nodes": ( + "sqlbuild.executor.python_nodes.main._ingress", + "run_ingress_python_loader_nodes", + ), + "create_read_side_python_execution_tracker": ( + "sqlbuild.executor.python_nodes.main.read_side", + "create_read_side_python_execution_tracker", + ), + "IngressCallbacks": ("sqlbuild.executor.python_nodes.models", "IngressCallbacks"), + "PythonCheckCallbacks": ("sqlbuild.executor.python_nodes.models", "PythonCheckCallbacks"), + "PythonCheckExecutionResult": ( + "sqlbuild.executor.python_nodes.models", + "PythonCheckExecutionResult", + ), + "PythonNodeExecutionResult": ( + "sqlbuild.executor.python_nodes.models", + "PythonNodeExecutionResult", + ), + "PythonNodeRunState": ("sqlbuild.executor.python_nodes.models", "PythonNodeRunState"), + "PythonNodeRuntime": ("sqlbuild.executor.python_nodes.models", "PythonNodeRuntime"), + "ModelExecutionResult": ("sqlbuild.executor.run.models", "ModelExecutionResult"), + "ScenarioCaptureSettings": ("sqlbuild.executor.scenario.models", "ScenarioCaptureSettings"), + "ScenarioLocalReplaySource": ("sqlbuild.executor.scenario.models", "ScenarioLocalReplaySource"), + "ScenarioSnapshotCaptureLimits": ( + "sqlbuild.executor.scenario.models", + "ScenarioSnapshotCaptureLimits", + ), + "ExecutionStatus": ("sqlbuild.executor.scheduling.types", "ExecutionStatus"), + "SqlTestExecutionResult": ("sqlbuild.executor.testing.models", "SqlTestExecutionResult"), + "MicrobatchEventCodec": ("sqlbuild.microbatches.classes.event_codec", "MicrobatchEventCodec"), + "MICROBATCH_COLUMNS": ("sqlbuild.microbatches.constants", "MICROBATCH_COLUMNS"), + "MICROBATCH_COLUMN_TYPES": ("sqlbuild.microbatches.constants", "MICROBATCH_COLUMN_TYPES"), + "MICROBATCH_DIRECT_INDEXES": ("sqlbuild.microbatches.constants", "MICROBATCH_DIRECT_INDEXES"), + "MICROBATCH_GENERATION_WILDCARD": ( + "sqlbuild.microbatches.constants", + "MICROBATCH_GENERATION_WILDCARD", + ), + "MICROBATCH_TABLE_NAME": ("sqlbuild.microbatches.constants", "MICROBATCH_TABLE_NAME"), + "MicrobatchStateError": ("sqlbuild.microbatches.exceptions", "MicrobatchStateError"), + "MicrobatchCoverageProjection": ( + "sqlbuild.microbatches.models", + "MicrobatchCoverageProjection", + ), + "MicrobatchInterval": ("sqlbuild.microbatches.models", "MicrobatchInterval"), + "ProjectedMicrobatchInterval": ("sqlbuild.microbatches.models", "ProjectedMicrobatchInterval"), + "ReplayRequirementProjection": ("sqlbuild.microbatches.models", "ReplayRequirementProjection"), + "ReplayRequirementState": ("sqlbuild.microbatches.types", "ReplayRequirementState"), + "TextComputeLogTee": ("sqlbuild.runtime.compute_logs.classes.text_tee", "TextComputeLogTee"), + "CaptureAlreadyExistsError": ( + "sqlbuild.runtime.compute_logs.exceptions", + "CaptureAlreadyExistsError", + ), + "CaptureNotFoundError": ("sqlbuild.runtime.compute_logs.exceptions", "CaptureNotFoundError"), + "CaptureStateError": ("sqlbuild.runtime.compute_logs.exceptions", "CaptureStateError"), + "ComputeLogMetadataError": ( + "sqlbuild.runtime.compute_logs.exceptions", + "ComputeLogMetadataError", + ), + "ComputeLogPathError": ("sqlbuild.runtime.compute_logs.exceptions", "ComputeLogPathError"), + "InvalidCaptureIdError": ("sqlbuild.runtime.compute_logs.exceptions", "InvalidCaptureIdError"), + "InvalidComputeLogCursorError": ( + "sqlbuild.runtime.compute_logs.exceptions", + "InvalidComputeLogCursorError", + ), + "InvalidComputeLogLimitError": ( + "sqlbuild.runtime.compute_logs.exceptions", + "InvalidComputeLogLimitError", + ), + "CaptureByteCounts": ("sqlbuild.runtime.compute_logs.models", "CaptureByteCounts"), + "CaptureInventory": ("sqlbuild.runtime.compute_logs.models", "CaptureInventory"), + "CaptureInventoryItem": ("sqlbuild.runtime.compute_logs.models", "CaptureInventoryItem"), + "CaptureMetadata": ("sqlbuild.runtime.compute_logs.models", "CaptureMetadata"), + "ComputeLogReadChunk": ("sqlbuild.runtime.compute_logs.models", "ComputeLogReadChunk"), + "FinalCaptureMetadata": ("sqlbuild.runtime.compute_logs.models", "FinalCaptureMetadata"), + "PruneResult": ("sqlbuild.runtime.compute_logs.models", "PruneResult"), + "ComputeLogStream": ("sqlbuild.runtime.compute_logs.types", "ComputeLogStream"), + "ConnectionHooks": ("sqlbuild.runtime.contracts.models", "ConnectionHooks"), + "ExecutionResourceKind": ("sqlbuild.runtime.contracts.types", "ExecutionResourceKind"), + "ExecutionHistoryStorageError": ( + "sqlbuild.runtime.execution_history.exceptions", + "ExecutionHistoryStorageError", + ), + "IntegrityConflictError": ( + "sqlbuild.runtime.execution_history.exceptions", + "IntegrityConflictError", + ), + "InvalidCursorError": ("sqlbuild.runtime.execution_history.exceptions", "InvalidCursorError"), + "InvalidEventError": ("sqlbuild.runtime.execution_history.exceptions", "InvalidEventError"), + "InvalidFilterError": ("sqlbuild.runtime.execution_history.exceptions", "InvalidFilterError"), + "InvalidLimitError": ("sqlbuild.runtime.execution_history.exceptions", "InvalidLimitError"), + "ProjectionConsistencyError": ( + "sqlbuild.runtime.execution_history.exceptions", + "ProjectionConsistencyError", + ), + "UnsupportedSchemaVersionError": ( + "sqlbuild.runtime.execution_history.exceptions", + "UnsupportedSchemaVersionError", + ), + "EventPage": ("sqlbuild.runtime.execution_history.models", "EventPage"), + "RunPage": ("sqlbuild.runtime.execution_history.models", "RunPage"), + "RunRecord": ("sqlbuild.runtime.execution_history.models", "RunRecord"), + "StoredEvent": ("sqlbuild.runtime.execution_history.models", "StoredEvent"), + "RunStatus": ("sqlbuild.runtime.execution_history.types", "RunStatus"), + "ExecutionDurationLimitError": ( + "sqlbuild.runtime.execution_limits.exceptions", + "ExecutionDurationLimitError", + ), + "execution_deadline_scope": ( + "sqlbuild.runtime.execution_limits.main.execution_deadline_scope", + "execution_deadline_scope", + ), + "EventDispatcher": ( + "sqlbuild.runtime.observability.classes.event_dispatcher", + "EventDispatcher", + ), + "OperationLifecycle": ( + "sqlbuild.runtime.observability.classes.operation_lifecycle", + "OperationLifecycle", + ), + "ObservabilityValidationError": ( + "sqlbuild.runtime.observability.exceptions", + "ObservabilityValidationError", + ), + "create_lifecycle_event": ( + "sqlbuild.runtime.observability.main.create_lifecycle_event", + "create_lifecycle_event", + ), + "current_execution_identity": ( + "sqlbuild.runtime.observability.main.current_execution_identity", + "current_execution_identity", + ), + "diagnostic_log_to_json": ( + "sqlbuild.runtime.observability.main.diagnostic_log_to_json", + "diagnostic_log_to_json", + ), + "dispatcher_scope": ( + "sqlbuild.runtime.observability.main.dispatcher_scope", + "dispatcher_scope", + ), + "invocation_external_context_scope": ( + "sqlbuild.runtime.observability.main.invocation_external_context_scope", + "invocation_external_context_scope", + ), + "invocation_scope": ( + "sqlbuild.runtime.observability.main.invocation_scope", + "invocation_scope", + ), + "lifecycle_event_from_json": ( + "sqlbuild.runtime.observability.main.lifecycle_event_from_json", + "lifecycle_event_from_json", + ), + "lifecycle_event_to_json": ( + "sqlbuild.runtime.observability.main.lifecycle_event_to_json", + "lifecycle_event_to_json", + ), + "run_scope": ("sqlbuild.runtime.observability.main.run_scope", "run_scope"), + "DiagnosticLog": ("sqlbuild.runtime.observability.models", "DiagnosticLog"), + "LifecycleEvent": ("sqlbuild.runtime.observability.models", "LifecycleEvent"), + "OperationAttributes": ("sqlbuild.runtime.observability.models", "OperationAttributes"), + "CommandOutputValidationError": ( + "sqlbuild.runtime.output_capture.exceptions", + "CommandOutputValidationError", + ), + "OutputCaptureInputError": ( + "sqlbuild.runtime.output_capture.exceptions", + "OutputCaptureInputError", + ), + "configured_output_capture_scope": ( + "sqlbuild.runtime.output_capture.main.configured_output_capture_scope", + "configured_output_capture_scope", + ), + "BoundCommandOutputSink": ("sqlbuild.runtime.output_capture.models", "BoundCommandOutputSink"), + "CommandOutputCaptureSummary": ( + "sqlbuild.runtime.output_capture.models", + "CommandOutputCaptureSummary", + ), + "CommandOutputRecord": ("sqlbuild.runtime.output_capture.models", "CommandOutputRecord"), + "OutputCaptureContext": ("sqlbuild.runtime.output_capture.models", "OutputCaptureContext"), + "CommandOutputStream": ("sqlbuild.runtime.output_capture.types", "CommandOutputStream"), + "OutputRecordPriority": ("sqlbuild.runtime.output_capture.types", "OutputRecordPriority"), + "SqlValueRenderingError": ("sqlbuild.sql_values.exceptions", "SqlValueRenderingError"), + "ASSET_SELECTION_COMMANDS": ( + "sqlbuild.integrations.dagster.constants", + "ASSET_SELECTION_COMMANDS", + ), + "CHECK_METADATA_EXCLUDED_KEYS": ( + "sqlbuild.integrations.dagster.constants", + "CHECK_METADATA_EXCLUDED_KEYS", + ), + "CLONE_COMMAND": ("sqlbuild.integrations.dagster.constants", "CLONE_COMMAND"), + "COMPLETED_EXECUTION_STATUSES": ( + "sqlbuild.integrations.dagster.constants", + "COMPLETED_EXECUTION_STATUSES", + ), + "DAGSTER_ASSET_ENABLED_FIELD": ( + "sqlbuild.integrations.dagster.constants", + "DAGSTER_ASSET_ENABLED_FIELD", + ), + "DAGSTER_ASSET_NODE_KINDS": ( + "sqlbuild.integrations.dagster.constants", + "DAGSTER_ASSET_NODE_KINDS", + ), + "DAGSTER_CHECK_ENABLED_FIELD": ( + "sqlbuild.integrations.dagster.constants", + "DAGSTER_CHECK_ENABLED_FIELD", + ), + "DAGSTER_CHECK_NAME_FIELD": ( + "sqlbuild.integrations.dagster.constants", + "DAGSTER_CHECK_NAME_FIELD", + ), + "DAGSTER_DIRECT_KIND_NODE_KINDS": ( + "sqlbuild.integrations.dagster.constants", + "DAGSTER_DIRECT_KIND_NODE_KINDS", + ), + "DEFAULT_SELECTABLE_NODE_KINDS": ( + "sqlbuild.integrations.dagster.constants", + "DEFAULT_SELECTABLE_NODE_KINDS", + ), + "EXPLICIT_SELECTION_FLAGS": ( + "sqlbuild.integrations.dagster.constants", + "EXPLICIT_SELECTION_FLAGS", + ), + "INSUFFICIENT_CHECK_STATUS": ( + "sqlbuild.integrations.dagster.constants", + "INSUFFICIENT_CHECK_STATUS", + ), + "LIVE_EVENT_COMMANDS": ("sqlbuild.integrations.dagster.constants", "LIVE_EVENT_COMMANDS"), + "LOADER_NODE_KIND": ("sqlbuild.integrations.dagster.constants", "LOADER_NODE_KIND"), + "LOAD_SELECTABLE_NODE_KINDS": ( + "sqlbuild.integrations.dagster.constants", + "LOAD_SELECTABLE_NODE_KINDS", + ), + "MATERIALIZABLE_NODE_KINDS": ( + "sqlbuild.integrations.dagster.constants", + "MATERIALIZABLE_NODE_KINDS", + ), + "MODEL_NODE_KIND": ("sqlbuild.integrations.dagster.constants", "MODEL_NODE_KIND"), + "SCENARIO_CHECK_KIND": ("sqlbuild.integrations.dagster.constants", "SCENARIO_CHECK_KIND"), + "SOURCE_NODE_KIND": ("sqlbuild.integrations.dagster.constants", "SOURCE_NODE_KIND"), + "STANDALONE_CHECK_COMMANDS": ( + "sqlbuild.integrations.dagster.constants", + "STANDALONE_CHECK_COMMANDS", + ), + "SUCCESS_EXECUTION_STATUS": ( + "sqlbuild.integrations.dagster.constants", + "SUCCESS_EXECUTION_STATUS", + ), + "VIEW_MATERIALIZATION_TYPE": ( + "sqlbuild.integrations.dagster.constants", + "VIEW_MATERIALIZATION_TYPE", + ), + "WARNING_CHECK_SEVERITY": ("sqlbuild.integrations.dagster.constants", "WARNING_CHECK_SEVERITY"), + "DagsterDagInputError": ("sqlbuild.integrations.dagster.exceptions", "DagsterDagInputError"), + "load_dbt_yaml": ("sqlbuild.integrations.dbt._helpers.profile.load", "load_dbt_yaml"), + "render_selected_dbt_profile_output": ( + "sqlbuild.integrations.dbt._helpers.profile.render", + "render_selected_dbt_profile_output", + ), + "DbtProfileError": ("sqlbuild.integrations.dbt.exceptions", "DbtProfileError"), + "DLT_INTEGRATION_KIND": ("sqlbuild.integrations.dlt.constants", "DLT_INTEGRATION_KIND"), + "DltIntegrationError": ("sqlbuild.integrations.dlt.exceptions", "DltIntegrationError"), + "dlt_loader_function": ("sqlbuild.integrations.dlt.main._loaders", "dlt_loader_function"), + "DltResourceConfig": ("sqlbuild.integrations.dlt.models", "DltResourceConfig"), + "DltSourceConfig": ("sqlbuild.integrations.dlt.models", "DltSourceConfig"), + "INGESTR_INTEGRATION_KIND": ( + "sqlbuild.integrations.ingestr.constants", + "INGESTR_INTEGRATION_KIND", + ), + "IngestrIntegrationError": ( + "sqlbuild.integrations.ingestr.exceptions", + "IngestrIntegrationError", + ), + "ingestr_loader_function": ( + "sqlbuild.integrations.ingestr.main._loaders", + "ingestr_loader_function", + ), + "IngestrSourceConfig": ("sqlbuild.integrations.ingestr.models", "IngestrSourceConfig"), + "RIVERS_ASSET_NODE_KINDS": ( + "sqlbuild.integrations.rivers.constants", + "RIVERS_ASSET_NODE_KINDS", + ), + "package_file": ("sqlbuild", "__file__"), + "dispatch_with_compute_logs": ( + "sqlbuild.cli.commands.main.entrypoint._dispatch_with_compute_logs", + "dispatch_with_compute_logs", + ), + "provider_settings_inputs": ( + "sqlbuild.cli.compile_reuse._helpers.provider_settings", + "provider_settings_inputs", + ), + "startup_inputs": ("sqlbuild.cli.compile_reuse._helpers.startup_inputs", "startup_inputs"), + "CompileReuseRequest": ("sqlbuild.cli.compile_reuse.models", "CompileReuseRequest"), +} + +__all__ = ("host_targets", *_TARGETS) + + +def host_targets() -> dict[str, tuple[str, str]]: + """Each host name with the module and attribute it resolves to.""" + return dict(_TARGETS) + + +def __getattr__(name: str) -> object: + """Resolve `name` from its target module on every read.""" + target: tuple[str, str] | None = _TARGETS.get(name) + if target is None: + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") + module, attribute = target + return getattr(importlib.import_module(module), attribute) diff --git a/src/sqlbuild/_native_host.pyi b/src/sqlbuild/_native_host.pyi new file mode 100644 index 0000000000..562d33924e --- /dev/null +++ b/src/sqlbuild/_native_host.pyi @@ -0,0 +1,576 @@ +"""The staying-Python entry points native code calls, resolved lazily by name.""" + +from sqlbuild.adapter.contract.classes.base_adapter import BaseAdapter as BaseAdapter +from sqlbuild.adapter.contract.exceptions import AdapterUserError as AdapterUserError +from sqlbuild.adapter.contract.models import ColumnInfo as ColumnInfo +from sqlbuild.adapter.contract.models import ( + ExpressionInferenceProfile as ExpressionInferenceProfile, +) +from sqlbuild.adapter.contract.models import RelationInfo as RelationInfo +from sqlbuild.adapter.contract.models import RelationLookup as RelationLookup +from sqlbuild.adapter.contract.models import RetentionRequest as RetentionRequest +from sqlbuild.adapter.contract.models import RowDiffSampling as RowDiffSampling +from sqlbuild.adapter.contract.models import RowDiffTolerance as RowDiffTolerance +from sqlbuild.adapter.contract.models import RowDiffTolerances as RowDiffTolerances +from sqlbuild.adapter.contract.models import TableFreshnessMetadata as TableFreshnessMetadata +from sqlbuild.adapter.contract.models import TableFreshnessRequest as TableFreshnessRequest +from sqlbuild.adapter.contract.types import BuiltinAdapter as BuiltinAdapter +from sqlbuild.adapter.contract.types import FrameworkType as FrameworkType +from sqlbuild.adapter.contract.types import LifeCycleEventKind as LifeCycleEventKind +from sqlbuild.adapter.contract.types import MigrationTransfer as MigrationTransfer +from sqlbuild.adapter.contract.types import RetentionScope as RetentionScope +from sqlbuild.adapter.contract.types import TableFreshnessStatus as TableFreshnessStatus +from sqlbuild.adapter.discovery.main._session_warehouse_support import ( + adapter_supports_session_warehouse as adapter_supports_session_warehouse, +) +from sqlbuild.adapter.discovery.main.builtins import ( + builtin_adapter_classes as builtin_adapter_classes, +) +from sqlbuild.adapter.discovery.main.project_adapters import ( + discover_project_adapters as discover_project_adapters, +) +from sqlbuild.adapter.discovery.main.resolve_adapter import resolve_adapter as resolve_adapter +from sqlbuild.adapter.relations.main._get_columns_for_inspection import ( + get_columns_for_inspection as get_columns_for_inspection, +) +from sqlbuild.adapter.relations.main._list_relations_for_inspection import ( + list_relations_for_inspection as list_relations_for_inspection, +) +from sqlbuild.adapter.relations.main.active_inspection_catalog import ( + active_inspection_catalog as active_inspection_catalog, +) +from sqlbuild.adapter.relations.main.inspection_query_sink import ( + inspection_query_sink as inspection_query_sink, +) +from sqlbuild.adapter.relations.main.open_inspection_catalog import ( + open_inspection_catalog as open_inspection_catalog, +) +from sqlbuild.adapter.relations.main.record_inspection_query import ( + record_inspection_query as record_inspection_query, +) +from sqlbuild.adapter.relations.main.relation_lookup import ( + build_relation_lookup as build_relation_lookup, +) +from sqlbuild.adapter.relations.main.resolve_relation_location_qualified_name import ( + resolve_relation_location_qualified_name as resolve_relation_location_qualified_name, +) +from sqlbuild.adapter.relations.main.run_bounded_inspections import ( + run_bounded_inspections as run_bounded_inspections, +) +from sqlbuild.adapter.relations.main.run_recorded_inspection_query import ( + run_recorded_inspection_query as run_recorded_inspection_query, +) +from sqlbuild.adapter.relations.models import InspectionQueryRecord as InspectionQueryRecord +from sqlbuild.adapter.relations.models import StatementMetadataEffect as StatementMetadataEffect +from sqlbuild.adapter.type_system.main.conditional_result_nullability import ( + conditional_result_nullability as conditional_result_nullability, +) +from sqlbuild.adapter.type_system.main.first_arg_nullability import ( + first_arg_nullability as first_arg_nullability, +) +from sqlbuild.adapter.type_system.main.normalize_relation_type import ( + normalize_relation_type as normalize_relation_type, +) +from sqlbuild.adapter.type_system.main.normalize_type import normalize_type as normalize_type +from sqlbuild.adapter.type_system.main.safe_cast_nullability import ( + safe_cast_nullability as safe_cast_nullability, +) +from sqlbuild.cli.commands.main.entrypoint._dispatch_with_compute_logs import ( + dispatch_with_compute_logs as dispatch_with_compute_logs, +) +from sqlbuild.cli.compile_reuse._helpers.provider_settings import ( + provider_settings_inputs as provider_settings_inputs, +) +from sqlbuild.cli.compile_reuse._helpers.startup_inputs import startup_inputs as startup_inputs +from sqlbuild.cli.compile_reuse.models import CompileReuseRequest as CompileReuseRequest +from sqlbuild.compiler.compile._helpers.diagnostics.python_resources import ( + python_resource_rows as python_resource_rows, +) +from sqlbuild.compiler.compile._helpers.macro_bridge.call_store import ( + target_context_with_macro_call_store as target_context_with_macro_call_store, +) +from sqlbuild.compiler.compile._helpers.render.user_macros import ( + inventory_project_macros as inventory_project_macros, +) +from sqlbuild.compiler.compile._helpers.render.user_macros import ( + load_project_macros as load_project_macros, +) +from sqlbuild.compiler.compile._helpers.render.user_macros import ( + macro_invocation_context as macro_invocation_context, +) +from sqlbuild.compiler.discovery._helpers.filesystem.command_output_sinks import ( + discover_command_output_sink_functions as discover_command_output_sink_functions, +) +from sqlbuild.compiler.discovery._helpers.filesystem.project_modules import ( + import_python_root_module as import_python_root_module, +) +from sqlbuild.compiler.discovery._helpers.filesystem.project_modules import ( + install_python_root_package as install_python_root_package, +) +from sqlbuild.compiler.discovery._helpers.filesystem.project_modules import ( + load_materialize_function as load_materialize_function, +) +from sqlbuild.compiler.discovery._helpers.filesystem.project_modules import ( + load_provider_module as load_provider_module, +) +from sqlbuild.compiler.discovery._helpers.filesystem.project_modules import ( + load_python_node_module as load_python_node_module, +) +from sqlbuild.compiler.discovery._helpers.filesystem.project_modules import ( + load_sink_module as load_sink_module, +) +from sqlbuild.compiler.discovery._helpers.filesystem.project_modules import ( + module_functions as module_functions, +) +from sqlbuild.compiler.discovery._helpers.filesystem.project_modules import ( + module_provider_classes as module_provider_classes, +) +from sqlbuild.compiler.discovery._helpers.filesystem.project_modules import ( + parameter_annotations as parameter_annotations, +) +from sqlbuild.compiler.discovery._helpers.filesystem.project_modules import ( + provider_usages as provider_usages, +) +from sqlbuild.compiler.discovery._helpers.filesystem.project_modules import ( + requires_arguments as requires_arguments, +) +from sqlbuild.compiler.discovery._helpers.filesystem.project_modules import ( + restore_python_root_modules as restore_python_root_modules, +) +from sqlbuild.compiler.discovery._helpers.filesystem.project_modules import ( + source_line as source_line, +) +from sqlbuild.compiler.discovery._helpers.filesystem.project_modules import ( + validate_event_exporter_signature as validate_event_exporter_signature, +) +from sqlbuild.compiler.discovery._helpers.validation.discovery import ( + validate_discovered_inputs as validate_discovered_inputs, +) +from sqlbuild.compiler.discovery.main.runtime_extensions import ( + discover_runtime_extensions as discover_runtime_extensions, +) +from sqlbuild.compiler.macro_bridge.classes.macro_bridge import ( + run_with_macro_bridge as run_with_macro_bridge, +) +from sqlbuild.compiler.macro_bridge.main._macro_store_module_paths import ( + macro_store_module_paths as macro_store_module_paths, +) +from sqlbuild.compiler.macro_bridge.main.active_macro_bridge import ( + active_macro_bridge as active_macro_bridge, +) +from sqlbuild.compiler.pipeline._helpers.materializations import ( + load_custom_materializations as load_custom_materializations, +) +from sqlbuild.compiler.python_nodes._helpers.user_inventory import ( + python_node_dependency_key as python_node_dependency_key, +) +from sqlbuild.compiler.python_nodes._helpers.user_inventory import ( + python_node_identity as python_node_identity, +) +from sqlbuild.compiler.python_nodes.main._hook_identities import ( + build_hook_identities as build_hook_identities, +) +from sqlbuild.compiler.python_nodes.types import PythonNodeKind as PythonNodeKind +from sqlbuild.compiler.python_nodes.types import PythonNodeStatus as PythonNodeStatus +from sqlbuild.cost.models import CostResourceContext as CostResourceContext +from sqlbuild.cost.models import RunCostSummary as RunCostSummary +from sqlbuild.cost.types import CostCapability as CostCapability +from sqlbuild.cost.types import CostStatus as CostStatus +from sqlbuild.cursor_algebra.types import BoundSentinel as BoundSentinel +from sqlbuild.diagnostics._helpers.process_resources import ( + read_process_resources as read_process_resources, +) +from sqlbuild.diagnostics.constants import SQL_PRIVATE_RECORD_MARKER as SQL_PRIVATE_RECORD_MARKER +from sqlbuild.diagnostics.main.diagnostics_context import diagnostics_context as diagnostics_context +from sqlbuild.diagnostics.main.log_debug_event import log_debug_event as log_debug_event +from sqlbuild.diagnostics.main.log_sql import log_sql as log_sql +from sqlbuild.diagnostics.main.process_resource_reporting import ( + process_resource_reporting as process_resource_reporting, +) +from sqlbuild.diagnostics.models import ProcessResourceUsage as ProcessResourceUsage +from sqlbuild.errors.contracts.exceptions import ExecutorInputError as ExecutorInputError +from sqlbuild.executor.auditing.main._current_audit_result_projection import ( + current_audit_result_projection as current_audit_result_projection, +) +from sqlbuild.executor.auditing.models import AuditExecutionResult as AuditExecutionResult +from sqlbuild.executor.build.models import BuildCallbacks as BuildCallbacks +from sqlbuild.executor.build.models import BuildCustomizations as BuildCustomizations +from sqlbuild.executor.build.models import BuildExecutionResult as BuildExecutionResult +from sqlbuild.executor.build.models import BuildInitialState as BuildInitialState +from sqlbuild.executor.build.models import BuildRuntimeParams as BuildRuntimeParams +from sqlbuild.executor.build.models import FunctionExecutionResult as FunctionExecutionResult +from sqlbuild.executor.build.models import SeedExecutionResult as SeedExecutionResult +from sqlbuild.executor.build.types import BuildStatus as BuildStatus +from sqlbuild.executor.clone.main._build_destination_transient_models import ( + build_destination_transient_models as build_destination_transient_models, +) +from sqlbuild.executor.clone.main._build_retention_requests import ( + build_destination_retention_requests as build_destination_retention_requests, +) +from sqlbuild.executor.clone.main._fingerprinting import ( + copy_clone_fingerprints as copy_clone_fingerprints, +) +from sqlbuild.executor.clone.main._run_prephase_clone_stream import ( + run_prephase_clone_stream as run_prephase_clone_stream, +) +from sqlbuild.executor.clone.main.execute import execute_clone as execute_clone +from sqlbuild.executor.clone.models import CloneExecutionInput as CloneExecutionInput +from sqlbuild.executor.clone.models import CloneItemResult as CloneItemResult +from sqlbuild.executor.clone.models import CloneSourceEntries as CloneSourceEntries +from sqlbuild.executor.clone.types import CloneStatus as CloneStatus +from sqlbuild.executor.diff.classes.query_artifact_lifecycle import ( + QueryDiffArtifactLifecycle as QueryDiffArtifactLifecycle, +) +from sqlbuild.executor.diff.exceptions import FullDiffSizeGuardError as FullDiffSizeGuardError +from sqlbuild.executor.diff.main._execute_query import execute_query_diff as execute_query_diff +from sqlbuild.executor.diff.main.execute import execute_diff as execute_diff +from sqlbuild.executor.diff.models import DiffExecutionOptions as DiffExecutionOptions +from sqlbuild.executor.diff.models import DiffExecutionResult as DiffExecutionResult +from sqlbuild.executor.diff.models import FullDiffSizeLimits as FullDiffSizeLimits +from sqlbuild.executor.diff.models import RowDiffSamplingOverride as RowDiffSamplingOverride +from sqlbuild.executor.janitor.main.execute import execute_janitor_plan as execute_janitor_plan +from sqlbuild.executor.janitor.models import JanitorArchiveCandidate as JanitorArchiveCandidate +from sqlbuild.executor.janitor.models import JanitorArchivedRelation as JanitorArchivedRelation +from sqlbuild.executor.janitor.models import JanitorBlockedSchema as JanitorBlockedSchema +from sqlbuild.executor.janitor.models import JanitorDeleteCandidate as JanitorDeleteCandidate +from sqlbuild.executor.janitor.models import ( + JanitorDirectStatePruneCandidate as JanitorDirectStatePruneCandidate, +) +from sqlbuild.executor.janitor.models import JanitorOldNameView as JanitorOldNameView +from sqlbuild.executor.janitor.models import ( + JanitorOldNameViewPlanning as JanitorOldNameViewPlanning, +) +from sqlbuild.executor.janitor.models import JanitorPlan as JanitorPlan +from sqlbuild.executor.janitor.models import ( + JanitorQueryDiffArtifactCandidate as JanitorQueryDiffArtifactCandidate, +) +from sqlbuild.executor.janitor.models import JanitorRelationKey as JanitorRelationKey +from sqlbuild.executor.janitor.models import JanitorSkippedRelation as JanitorSkippedRelation +from sqlbuild.executor.janitor.models import JanitorSkippedSchema as JanitorSkippedSchema +from sqlbuild.executor.load.main.run import run_load_pipeline as run_load_pipeline +from sqlbuild.executor.load.models import LoadCallbacks as LoadCallbacks +from sqlbuild.executor.load.models import LoadExecutionResult as LoadExecutionResult +from sqlbuild.executor.load.models import LoadRuntimeParams as LoadRuntimeParams +from sqlbuild.executor.migrations._helpers.relation_for_location import ( + migration_relation_for_location as migration_relation_for_location, +) +from sqlbuild.executor.old_name_views._helpers.janitor import ( + is_compatibility_view as is_compatibility_view, +) +from sqlbuild.executor.pipeline.exceptions import AuditExecutionError as AuditExecutionError +from sqlbuild.executor.pipeline.main.run import AuditPipelineCallbacks as AuditPipelineCallbacks +from sqlbuild.executor.pipeline.main.run import ( + run_audit_pipeline_with_callbacks as run_audit_pipeline_with_callbacks, +) +from sqlbuild.executor.pipeline.main.run import run_build_pipeline as run_build_pipeline +from sqlbuild.executor.pipeline.main.run import ( + run_scenario_capture_pipeline as run_scenario_capture_pipeline, +) +from sqlbuild.executor.pipeline.main.run import ( + run_scenario_local_test_pipeline as run_scenario_local_test_pipeline, +) +from sqlbuild.executor.pipeline.main.run import ( + run_scenario_test_pipeline as run_scenario_test_pipeline, +) +from sqlbuild.executor.pipeline.main.run import run_seed_pipeline as run_seed_pipeline +from sqlbuild.executor.pipeline.main.run import run_test_pipeline as run_test_pipeline +from sqlbuild.executor.pipeline.main.run import ( + select_scenario_snapshot_capture_candidates as select_scenario_snapshot_capture_candidates, +) +from sqlbuild.executor.pipeline.models import TestPipelineCallbacks as TestPipelineCallbacks +from sqlbuild.executor.python_nodes.main._checks import ( + execute_python_checks as execute_python_checks, +) +from sqlbuild.executor.python_nodes.main._ingress import ( + execute_ingress_python_loader_nodes as execute_ingress_python_loader_nodes, +) +from sqlbuild.executor.python_nodes.main._ingress import ( + run_ingress_python_loader_nodes as run_ingress_python_loader_nodes, +) +from sqlbuild.executor.python_nodes.main.read_side import ( + create_read_side_python_execution_tracker as create_read_side_python_execution_tracker, +) +from sqlbuild.executor.python_nodes.models import IngressCallbacks as IngressCallbacks +from sqlbuild.executor.python_nodes.models import PythonCheckCallbacks as PythonCheckCallbacks +from sqlbuild.executor.python_nodes.models import ( + PythonCheckExecutionResult as PythonCheckExecutionResult, +) +from sqlbuild.executor.python_nodes.models import ( + PythonNodeExecutionResult as PythonNodeExecutionResult, +) +from sqlbuild.executor.python_nodes.models import PythonNodeRunState as PythonNodeRunState +from sqlbuild.executor.python_nodes.models import PythonNodeRuntime as PythonNodeRuntime +from sqlbuild.executor.run.models import ModelExecutionResult as ModelExecutionResult +from sqlbuild.executor.scenario.models import ScenarioCaptureSettings as ScenarioCaptureSettings +from sqlbuild.executor.scenario.models import ScenarioLocalReplaySource as ScenarioLocalReplaySource +from sqlbuild.executor.scenario.models import ( + ScenarioSnapshotCaptureLimits as ScenarioSnapshotCaptureLimits, +) +from sqlbuild.executor.scheduling.types import ExecutionStatus as ExecutionStatus +from sqlbuild.executor.testing.models import SqlTestExecutionResult as SqlTestExecutionResult +from sqlbuild.integrations.dagster.constants import ( + ASSET_SELECTION_COMMANDS as ASSET_SELECTION_COMMANDS, +) +from sqlbuild.integrations.dagster.constants import ( + CHECK_METADATA_EXCLUDED_KEYS as CHECK_METADATA_EXCLUDED_KEYS, +) +from sqlbuild.integrations.dagster.constants import CLONE_COMMAND as CLONE_COMMAND +from sqlbuild.integrations.dagster.constants import ( + COMPLETED_EXECUTION_STATUSES as COMPLETED_EXECUTION_STATUSES, +) +from sqlbuild.integrations.dagster.constants import ( + DAGSTER_ASSET_ENABLED_FIELD as DAGSTER_ASSET_ENABLED_FIELD, +) +from sqlbuild.integrations.dagster.constants import ( + DAGSTER_ASSET_NODE_KINDS as DAGSTER_ASSET_NODE_KINDS, +) +from sqlbuild.integrations.dagster.constants import ( + DAGSTER_CHECK_ENABLED_FIELD as DAGSTER_CHECK_ENABLED_FIELD, +) +from sqlbuild.integrations.dagster.constants import ( + DAGSTER_CHECK_NAME_FIELD as DAGSTER_CHECK_NAME_FIELD, +) +from sqlbuild.integrations.dagster.constants import ( + DAGSTER_DIRECT_KIND_NODE_KINDS as DAGSTER_DIRECT_KIND_NODE_KINDS, +) +from sqlbuild.integrations.dagster.constants import ( + DEFAULT_SELECTABLE_NODE_KINDS as DEFAULT_SELECTABLE_NODE_KINDS, +) +from sqlbuild.integrations.dagster.constants import ( + EXPLICIT_SELECTION_FLAGS as EXPLICIT_SELECTION_FLAGS, +) +from sqlbuild.integrations.dagster.constants import ( + INSUFFICIENT_CHECK_STATUS as INSUFFICIENT_CHECK_STATUS, +) +from sqlbuild.integrations.dagster.constants import LIVE_EVENT_COMMANDS as LIVE_EVENT_COMMANDS +from sqlbuild.integrations.dagster.constants import ( + LOAD_SELECTABLE_NODE_KINDS as LOAD_SELECTABLE_NODE_KINDS, +) +from sqlbuild.integrations.dagster.constants import LOADER_NODE_KIND as LOADER_NODE_KIND +from sqlbuild.integrations.dagster.constants import ( + MATERIALIZABLE_NODE_KINDS as MATERIALIZABLE_NODE_KINDS, +) +from sqlbuild.integrations.dagster.constants import MODEL_NODE_KIND as MODEL_NODE_KIND +from sqlbuild.integrations.dagster.constants import SCENARIO_CHECK_KIND as SCENARIO_CHECK_KIND +from sqlbuild.integrations.dagster.constants import SOURCE_NODE_KIND as SOURCE_NODE_KIND +from sqlbuild.integrations.dagster.constants import ( + STANDALONE_CHECK_COMMANDS as STANDALONE_CHECK_COMMANDS, +) +from sqlbuild.integrations.dagster.constants import ( + SUCCESS_EXECUTION_STATUS as SUCCESS_EXECUTION_STATUS, +) +from sqlbuild.integrations.dagster.constants import ( + VIEW_MATERIALIZATION_TYPE as VIEW_MATERIALIZATION_TYPE, +) +from sqlbuild.integrations.dagster.constants import WARNING_CHECK_SEVERITY as WARNING_CHECK_SEVERITY +from sqlbuild.integrations.dagster.exceptions import DagsterDagInputError as DagsterDagInputError +from sqlbuild.integrations.dbt._helpers.profile.load import load_dbt_yaml as load_dbt_yaml +from sqlbuild.integrations.dbt._helpers.profile.render import ( + render_selected_dbt_profile_output as render_selected_dbt_profile_output, +) +from sqlbuild.integrations.dbt.exceptions import DbtProfileError as DbtProfileError +from sqlbuild.integrations.dlt.constants import DLT_INTEGRATION_KIND as DLT_INTEGRATION_KIND +from sqlbuild.integrations.dlt.exceptions import DltIntegrationError as DltIntegrationError +from sqlbuild.integrations.dlt.main._loaders import dlt_loader_function as dlt_loader_function +from sqlbuild.integrations.dlt.models import DltResourceConfig as DltResourceConfig +from sqlbuild.integrations.dlt.models import DltSourceConfig as DltSourceConfig +from sqlbuild.integrations.ingestr.constants import ( + INGESTR_INTEGRATION_KIND as INGESTR_INTEGRATION_KIND, +) +from sqlbuild.integrations.ingestr.exceptions import ( + IngestrIntegrationError as IngestrIntegrationError, +) +from sqlbuild.integrations.ingestr.main._loaders import ( + ingestr_loader_function as ingestr_loader_function, +) +from sqlbuild.integrations.ingestr.models import IngestrSourceConfig as IngestrSourceConfig +from sqlbuild.integrations.rivers.constants import ( + RIVERS_ASSET_NODE_KINDS as RIVERS_ASSET_NODE_KINDS, +) +from sqlbuild.microbatches.classes.event_codec import MicrobatchEventCodec as MicrobatchEventCodec +from sqlbuild.microbatches.constants import MICROBATCH_COLUMN_TYPES as MICROBATCH_COLUMN_TYPES +from sqlbuild.microbatches.constants import MICROBATCH_COLUMNS as MICROBATCH_COLUMNS +from sqlbuild.microbatches.constants import MICROBATCH_DIRECT_INDEXES as MICROBATCH_DIRECT_INDEXES +from sqlbuild.microbatches.constants import ( + MICROBATCH_GENERATION_WILDCARD as MICROBATCH_GENERATION_WILDCARD, +) +from sqlbuild.microbatches.constants import MICROBATCH_TABLE_NAME as MICROBATCH_TABLE_NAME +from sqlbuild.microbatches.exceptions import MicrobatchStateError as MicrobatchStateError +from sqlbuild.microbatches.models import ( + MicrobatchCoverageProjection as MicrobatchCoverageProjection, +) +from sqlbuild.microbatches.models import MicrobatchInterval as MicrobatchInterval +from sqlbuild.microbatches.models import ProjectedMicrobatchInterval as ProjectedMicrobatchInterval +from sqlbuild.microbatches.models import ReplayRequirementProjection as ReplayRequirementProjection +from sqlbuild.microbatches.types import ReplayRequirementState as ReplayRequirementState +from sqlbuild.provider.exceptions import ProviderInputError as ProviderInputError +from sqlbuild.provider.main.session import build_provider_session as build_provider_session +from sqlbuild.python_nodes.main.read_asset_definition import ( + read_asset_definition as read_asset_definition, +) +from sqlbuild.python_nodes.main.read_audit_factory_definition import ( + read_audit_factory_definition as read_audit_factory_definition, +) +from sqlbuild.python_nodes.main.read_check_definition import ( + read_check_definition as read_check_definition, +) +from sqlbuild.python_nodes.main.read_factory_definition import ( + read_factory_definition as read_factory_definition, +) +from sqlbuild.python_nodes.main.read_hook_definition import ( + read_hook_definition as read_hook_definition, +) +from sqlbuild.python_nodes.main.read_loader_definition import ( + read_loader_definition as read_loader_definition, +) +from sqlbuild.python_nodes.main.read_task_definition import ( + read_task_definition as read_task_definition, +) +from sqlbuild.python_nodes.models import RELATION_RENDERER as RELATION_RENDERER +from sqlbuild.python_nodes.models import AuditCase as AuditCase +from sqlbuild.python_nodes.models import SqlResourceRef as SqlResourceRef +from sqlbuild.python_nodes.types import PythonCheckSeverity as PythonCheckSeverity +from sqlbuild.python_nodes.types import SqlResourceRefKind as SqlResourceRefKind +from sqlbuild.rule_engine._helpers.engine._custom_catalogue import ( + load_custom_rules as load_custom_rules, +) +from sqlbuild.rule_engine._helpers.engine.custom_payloads import ( + custom_rule_payloads as custom_rule_payloads, +) +from sqlbuild.rule_engine._helpers.engine.fact_replay import ( + fact_outcome_digest as fact_outcome_digest, +) +from sqlbuild.rule_engine._helpers.engine.fact_replay import full_fact_keys as full_fact_keys +from sqlbuild.rule_engine._helpers.engine.fact_replay import is_tree_fact as is_tree_fact +from sqlbuild.rule_engine._helpers.engine.fact_replay import ( + shared_fact_encoder as shared_fact_encoder, +) +from sqlbuild.rule_engine.classes.rule_context import build_rule_fact_views as build_rule_fact_views +from sqlbuild.runtime.compute_logs.classes.text_tee import TextComputeLogTee as TextComputeLogTee +from sqlbuild.runtime.compute_logs.exceptions import ( + CaptureAlreadyExistsError as CaptureAlreadyExistsError, +) +from sqlbuild.runtime.compute_logs.exceptions import CaptureNotFoundError as CaptureNotFoundError +from sqlbuild.runtime.compute_logs.exceptions import CaptureStateError as CaptureStateError +from sqlbuild.runtime.compute_logs.exceptions import ( + ComputeLogMetadataError as ComputeLogMetadataError, +) +from sqlbuild.runtime.compute_logs.exceptions import ComputeLogPathError as ComputeLogPathError +from sqlbuild.runtime.compute_logs.exceptions import InvalidCaptureIdError as InvalidCaptureIdError +from sqlbuild.runtime.compute_logs.exceptions import ( + InvalidComputeLogCursorError as InvalidComputeLogCursorError, +) +from sqlbuild.runtime.compute_logs.exceptions import ( + InvalidComputeLogLimitError as InvalidComputeLogLimitError, +) +from sqlbuild.runtime.compute_logs.models import CaptureByteCounts as CaptureByteCounts +from sqlbuild.runtime.compute_logs.models import CaptureInventory as CaptureInventory +from sqlbuild.runtime.compute_logs.models import CaptureInventoryItem as CaptureInventoryItem +from sqlbuild.runtime.compute_logs.models import CaptureMetadata as CaptureMetadata +from sqlbuild.runtime.compute_logs.models import ComputeLogReadChunk as ComputeLogReadChunk +from sqlbuild.runtime.compute_logs.models import FinalCaptureMetadata as FinalCaptureMetadata +from sqlbuild.runtime.compute_logs.models import PruneResult as PruneResult +from sqlbuild.runtime.compute_logs.types import ComputeLogStream as ComputeLogStream +from sqlbuild.runtime.contracts.models import ConnectionHooks as ConnectionHooks +from sqlbuild.runtime.contracts.types import ExecutionResourceKind as ExecutionResourceKind +from sqlbuild.runtime.event_exporting.exceptions import ( + EventExporterInputError as EventExporterInputError, +) +from sqlbuild.runtime.event_exporting.exceptions import ( + EventExporterStateError as EventExporterStateError, +) +from sqlbuild.runtime.event_exporting.main._invoke_lifecycle_event_sink import ( + invoke_lifecycle_event_sink as invoke_lifecycle_event_sink, +) +from sqlbuild.runtime.event_exporting.main._run_command_output_sinks import ( + run_command_output_sinks as run_command_output_sinks, +) +from sqlbuild.runtime.event_exporting.main.get_lifecycle_event_sink_definition import ( + get_lifecycle_event_sink_definition as get_lifecycle_event_sink_definition, +) +from sqlbuild.runtime.execution_history.exceptions import ( + ExecutionHistoryStorageError as ExecutionHistoryStorageError, +) +from sqlbuild.runtime.execution_history.exceptions import ( + IntegrityConflictError as IntegrityConflictError, +) +from sqlbuild.runtime.execution_history.exceptions import InvalidCursorError as InvalidCursorError +from sqlbuild.runtime.execution_history.exceptions import InvalidEventError as InvalidEventError +from sqlbuild.runtime.execution_history.exceptions import InvalidFilterError as InvalidFilterError +from sqlbuild.runtime.execution_history.exceptions import InvalidLimitError as InvalidLimitError +from sqlbuild.runtime.execution_history.exceptions import ( + ProjectionConsistencyError as ProjectionConsistencyError, +) +from sqlbuild.runtime.execution_history.exceptions import ( + UnsupportedSchemaVersionError as UnsupportedSchemaVersionError, +) +from sqlbuild.runtime.execution_history.models import EventPage as EventPage +from sqlbuild.runtime.execution_history.models import RunPage as RunPage +from sqlbuild.runtime.execution_history.models import RunRecord as RunRecord +from sqlbuild.runtime.execution_history.models import StoredEvent as StoredEvent +from sqlbuild.runtime.execution_history.types import RunStatus as RunStatus +from sqlbuild.runtime.execution_limits.exceptions import ( + ExecutionDurationLimitError as ExecutionDurationLimitError, +) +from sqlbuild.runtime.execution_limits.main.execution_deadline_scope import ( + execution_deadline_scope as execution_deadline_scope, +) +from sqlbuild.runtime.observability.classes.event_dispatcher import ( + EventDispatcher as EventDispatcher, +) +from sqlbuild.runtime.observability.classes.operation_lifecycle import ( + OperationLifecycle as OperationLifecycle, +) +from sqlbuild.runtime.observability.exceptions import ( + ObservabilityValidationError as ObservabilityValidationError, +) +from sqlbuild.runtime.observability.main.create_lifecycle_event import ( + create_lifecycle_event as create_lifecycle_event, +) +from sqlbuild.runtime.observability.main.current_execution_identity import ( + current_execution_identity as current_execution_identity, +) +from sqlbuild.runtime.observability.main.diagnostic_log_to_json import ( + diagnostic_log_to_json as diagnostic_log_to_json, +) +from sqlbuild.runtime.observability.main.dispatcher_scope import ( + dispatcher_scope as dispatcher_scope, +) +from sqlbuild.runtime.observability.main.invocation_external_context_scope import ( + invocation_external_context_scope as invocation_external_context_scope, +) +from sqlbuild.runtime.observability.main.invocation_scope import ( + invocation_scope as invocation_scope, +) +from sqlbuild.runtime.observability.main.lifecycle_event_from_json import ( + lifecycle_event_from_json as lifecycle_event_from_json, +) +from sqlbuild.runtime.observability.main.lifecycle_event_to_json import ( + lifecycle_event_to_json as lifecycle_event_to_json, +) +from sqlbuild.runtime.observability.main.run_scope import run_scope as run_scope +from sqlbuild.runtime.observability.models import DiagnosticLog as DiagnosticLog +from sqlbuild.runtime.observability.models import LifecycleEvent as LifecycleEvent +from sqlbuild.runtime.observability.models import OperationAttributes as OperationAttributes +from sqlbuild.runtime.output_capture.exceptions import ( + CommandOutputValidationError as CommandOutputValidationError, +) +from sqlbuild.runtime.output_capture.exceptions import ( + OutputCaptureInputError as OutputCaptureInputError, +) +from sqlbuild.runtime.output_capture.main.configured_output_capture_scope import ( + configured_output_capture_scope as configured_output_capture_scope, +) +from sqlbuild.runtime.output_capture.models import BoundCommandOutputSink as BoundCommandOutputSink +from sqlbuild.runtime.output_capture.models import ( + CommandOutputCaptureSummary as CommandOutputCaptureSummary, +) +from sqlbuild.runtime.output_capture.models import CommandOutputRecord as CommandOutputRecord +from sqlbuild.runtime.output_capture.models import OutputCaptureContext as OutputCaptureContext +from sqlbuild.runtime.output_capture.types import CommandOutputStream as CommandOutputStream +from sqlbuild.runtime.output_capture.types import OutputRecordPriority as OutputRecordPriority +from sqlbuild.sql_values.exceptions import SqlValueRenderingError as SqlValueRenderingError + +package_file: str + +def host_targets() -> dict[str, tuple[str, str]]: ... diff --git a/src/sqlbuild/executor/pipeline/_helpers/scenario.py b/src/sqlbuild/executor/pipeline/_helpers/scenario.py index 47b2649c76..e7e8ec9329 100644 --- a/src/sqlbuild/executor/pipeline/_helpers/scenario.py +++ b/src/sqlbuild/executor/pipeline/_helpers/scenario.py @@ -2,8 +2,10 @@ from __future__ import annotations +import atexit import logging import queue +import sys import threading import time from collections.abc import Callable, Iterator @@ -68,6 +70,9 @@ _DEBUG_LOGGER: logging.Logger = logging.getLogger("sqlbuild.execution") _STOP_POLL_SECONDS: float = 0.05 +_WORKER_EXIT_JOIN_SECONDS: float = 5.0 +_WORKER_EXIT_NOTICE: str = "Waiting up to 5s for running scenario statements to stop...\n" +_WORKER_NAME_PREFIX: str = "sqlbuild-scenario-" _SCENARIO_INTERNAL_ERROR_HELP: str = ( "This is likely a SQLBuild bug. Please file an issue with the scenario name." ) @@ -558,17 +563,40 @@ def work() -> None: except BaseException as exc: # noqa: BLE001 - delivered through the future future.set_exception(exc) + atexit.unregister(_join_scenario_workers) + _ = atexit.register(_join_scenario_workers) context: Context = copy_context() worker: int for worker in range(worker_count): threading.Thread( target=context.copy().run, args=(work,), - name=f"sqlbuild-scenario-{worker}", + name=f"{_WORKER_NAME_PREFIX}{worker}", daemon=True, ).start() +def _join_scenario_workers() -> None: + """Wait a bounded time at exit for abandoned workers, so none is ended inside native code.""" + + workers: list[threading.Thread] = [ + thread + for thread in threading.enumerate() + if thread.name.startswith(_WORKER_NAME_PREFIX) and thread.is_alive() + ] + if not workers: + return + _ = sys.stderr.write(_WORKER_EXIT_NOTICE) + _ = sys.stderr.flush() + deadline: float = time.monotonic() + _WORKER_EXIT_JOIN_SECONDS + thread: threading.Thread + try: + for thread in workers: + thread.join(timeout=max(0.0, deadline - time.monotonic())) + except KeyboardInterrupt: + return + + def _stop_scenarios[OutcomeT]( *, error: BaseException, diff --git a/tests/unit/scripts/native_module_paths/_helpers/_test_types.py b/tests/unit/scripts/native_module_paths/_helpers/_test_types.py index db04ab1cdc..7040d2fcc8 100644 --- a/tests/unit/scripts/native_module_paths/_helpers/_test_types.py +++ b/tests/unit/scripts/native_module_paths/_helpers/_test_types.py @@ -13,3 +13,30 @@ class RepositoryModulePathTestCase: description: str expected_errors: tuple[str, ...] expected_logger_names: tuple[str, ...] + + +@dataclass(frozen=True) +class HostTableCase: + description: str + expected_duplicate_count: int + expected_missing_names: tuple[str, ...] + expected_extra_names: tuple[str, ...] + + +@dataclass(frozen=True) +class HostKindCase: + description: str + expected_mismatched_names: tuple[str, ...] + + +@dataclass(frozen=True) +class HostImportCase: + description: str + expected_loaded_modules: tuple[str, ...] + + +@dataclass(frozen=True) +class HostUnknownNameCase: + description: str + name: str + expected_error: str diff --git a/tests/unit/scripts/native_module_paths/_helpers/helpers.py b/tests/unit/scripts/native_module_paths/_helpers/helpers.py index f8c3865cc3..facfc3700e 100644 --- a/tests/unit/scripts/native_module_paths/_helpers/helpers.py +++ b/tests/unit/scripts/native_module_paths/_helpers/helpers.py @@ -7,3 +7,18 @@ def native_source_errors(source: str) -> tuple[str, ...]: """Scan one synthetic Rust file and resolve what it imports.""" return native_module_path_errors(scan=scan_native_source(source=source, label=SOURCE_LABEL)) + + +KIND_BY_TRAITS: dict[tuple[bool, bool, bool], str] = { + (True, True, True): "exception", + (True, False, True): "class", + (False, False, True): "function", + (False, False, False): "value", +} + + +def resolved_kind(value: object) -> str: + """The host kind of a resolved entry: class, exception, function or value.""" + is_class: bool = isinstance(value, type) + is_exception: bool = BaseException in getattr(value, "__mro__", ()) + return KIND_BY_TRAITS[(is_class, is_exception, callable(value))] diff --git a/tests/unit/scripts/native_module_paths/_helpers/test_module_paths.py b/tests/unit/scripts/native_module_paths/_helpers/test_module_paths.py index 936a3d35a1..f8e98b8787 100644 --- a/tests/unit/scripts/native_module_paths/_helpers/test_module_paths.py +++ b/tests/unit/scripts/native_module_paths/_helpers/test_module_paths.py @@ -46,6 +46,18 @@ ), expected_errors=(), ), + NativeSourceModulePathTestCase( + description="host table entry resolves through the host module", + source='const ENTRIES: &[HostEntry] = &[\n HostEntry {\n name: "BaseAdapter",\n },\n];\n', + expected_errors=(), + ), + NativeSourceModulePathTestCase( + description="host table entry the host module does not define", + source='const ENTRIES: &[HostEntry] = &[\n HostEntry {\n name: "no_such_entry",\n },\n];\n', + expected_errors=( + "lib.rs:2: 'sqlbuild._native_host' has no attribute 'no_such_entry'.", + ), + ), NativeSourceModulePathTestCase( description="unknown module literal", source='const MISSING_MODULE: &str = "sqlbuild.cli.no_such_module";\n', diff --git a/tests/unit/scripts/native_module_paths/_helpers/test_native_host.py b/tests/unit/scripts/native_module_paths/_helpers/test_native_host.py new file mode 100644 index 0000000000..5ff77bc5ee --- /dev/null +++ b/tests/unit/scripts/native_module_paths/_helpers/test_native_host.py @@ -0,0 +1,119 @@ +"""`sqlbuild._native_host` agrees with the native host table and imports its targets lazily.""" + +from __future__ import annotations + +import json +import subprocess +import sys + +import pytest + +import sqlbuild._native as native +import sqlbuild._native_host as native_host +from tests.unit.scripts.native_module_paths._helpers._test_types import ( + HostImportCase, + HostKindCase, + HostTableCase, + HostUnknownNameCase, +) +from tests.unit.scripts.native_module_paths._helpers.helpers import resolved_kind + + +@pytest.mark.parametrize( + "test_case", + [ + HostTableCase( + description="host names equal the native table", + expected_duplicate_count=0, + expected_missing_names=(), + expected_extra_names=(), + ) + ], + ids=lambda case: case.description, +) +def test_given_native_host_table_when_comparing_names_then_python_and_rust_agree( + test_case: HostTableCase, +) -> None: + listed: list[str] = [name for name, _, _ in native.HOST_ENTRIES] + native_names: set[str] = set(listed) + host_names: set[str] = set(native_host.host_targets()) + + assert len(listed) - len(native_names) == test_case.expected_duplicate_count + assert tuple(sorted(native_names - host_names)) == test_case.expected_missing_names + assert tuple(sorted(host_names - native_names)) == test_case.expected_extra_names + + +@pytest.mark.parametrize( + "test_case", + [ + HostKindCase( + description="every entry resolves to its declared kind", expected_mismatched_names=() + ) + ], + ids=lambda case: case.description, +) +def test_given_native_host_table_when_resolving_entries_then_each_has_its_declared_kind( + test_case: HostKindCase, +) -> None: + declared: dict[str, str] = {name: kind for name, kind, _ in native.HOST_ENTRIES} + resolved: dict[str, str] = { + name: resolved_kind(getattr(native_host, name)) for name in declared + } + + mismatched: set[tuple[str, str]] = set(resolved.items()) ^ set(declared.items()) + + assert tuple(sorted({name for name, _ in mismatched})) == test_case.expected_mismatched_names + + +@pytest.mark.parametrize( + "test_case", + [ + HostImportCase( + description="importing the host module loads none of its targets", + expected_loaded_modules=(), + ) + ], + ids=lambda case: case.description, +) +def test_given_clean_interpreter_when_importing_native_host_then_no_target_module_loads( + test_case: HostImportCase, +) -> None: + script: str = ( + "import json, sys\n" + "import sqlbuild\n" + "before = set(sys.modules)\n" + "import sqlbuild._native_host as host\n" + "targets = {module for module, _ in host.host_targets().values()}\n" + "loaded = sorted(targets & (set(sys.modules) - before))\n" + "print(json.dumps(loaded))\n" + ) + + result: subprocess.CompletedProcess[str] = subprocess.run( + [sys.executable, "-c", script], check=True, capture_output=True, text=True + ) + + assert tuple(json.loads(result.stdout)) == test_case.expected_loaded_modules + + +@pytest.mark.parametrize( + "test_case", + [ + HostUnknownNameCase( + description="a name outside the table is not an attribute", + name="no_such_entry", + expected_error="module 'sqlbuild._native_host' has no attribute 'no_such_entry'", + ) + ], + ids=lambda case: case.description, +) +def test_given_unknown_name_when_reading_native_host_then_raises_attribute_error( + test_case: HostUnknownNameCase, +) -> None: + with pytest.raises(AttributeError) as raised: + getattr(native_host, test_case.name) + + assert str(raised.value) == test_case.expected_error + + +if __name__ == "__main__": + pytest.main([__file__, "-vv"]) diff --git a/tests/unit/src/sqlbuild/executor/pipeline/_helpers/_test_types.py b/tests/unit/src/sqlbuild/executor/pipeline/_helpers/_test_types.py index 6222f977da..ea148bd256 100644 --- a/tests/unit/src/sqlbuild/executor/pipeline/_helpers/_test_types.py +++ b/tests/unit/src/sqlbuild/executor/pipeline/_helpers/_test_types.py @@ -176,3 +176,12 @@ class ConnectionPreparationTimingTestCase: expected_connection_seconds: float expected_schema_seconds: float expected_total_seconds: float + + +@dataclass(frozen=True) +class ScenarioWorkerExitTestCase: + """Scenario workers left inside native code when the interpreter exits, and the exit code.""" + + description: str + worker_count: int + expected_returncode: int diff --git a/tests/unit/src/sqlbuild/executor/pipeline/_helpers/test_scenario_interpreter_exit.py b/tests/unit/src/sqlbuild/executor/pipeline/_helpers/test_scenario_interpreter_exit.py new file mode 100644 index 0000000000..9eb52a13b0 --- /dev/null +++ b/tests/unit/src/sqlbuild/executor/pipeline/_helpers/test_scenario_interpreter_exit.py @@ -0,0 +1,77 @@ +import subprocess +import sys +from pathlib import Path +from textwrap import dedent + +import pytest + +from tests.unit.src.sqlbuild.executor.pipeline._helpers._test_types import ( + ScenarioWorkerExitTestCase, +) + +_ABANDONED_WORKERS: str = dedent( + """ + import sys + import time + import types + from concurrent.futures import Future + + + class SlowFinalizer: + def __del__(self): + print("finalizing", sys.is_finalizing(), file=sys.stderr, flush=True) + time.sleep(1.5) + + + holder = types.ModuleType("holder") + holder.finalizer = SlowFinalizer() + sys.modules["holder"] = holder + + from sqlbuild._native import invert_edges + from sqlbuild.executor.pipeline._helpers.scenario import _start_scenario_workers + + + def slow_key(item): + time.sleep(1.0) + return item + + + def run(*, scenario, notify_start): + return invert_edges(edges={"a": ("b",), "c": ("b",)}, sort_key=slow_key) + + + count = int(sys.argv[1]) + scenarios = tuple(f"scenario_{index}" for index in range(count)) + futures = tuple(Future() for _ in scenarios) + _start_scenario_workers(scenarios=scenarios, futures=futures, run=run, worker_count=count) + time.sleep(0.3) + """ +) + + +@pytest.mark.parametrize( + "test_case", + ( + ScenarioWorkerExitTestCase("one abandoned worker inside native code", 1, 0), + ScenarioWorkerExitTestCase("several abandoned workers inside native code", 3, 0), + ), + ids=lambda case: case.description, +) +def test_given_abandoned_scenario_workers_when_interpreter_finalizes_then_process_exits_cleanly( + test_case: ScenarioWorkerExitTestCase, + tmp_path: Path, +) -> None: + script: Path = tmp_path / "scenario_exit.py" + script.write_text(_ABANDONED_WORKERS, encoding="utf-8") + + result: subprocess.CompletedProcess[str] = subprocess.run( + [sys.executable, str(script), str(test_case.worker_count)], + capture_output=True, + text=True, + timeout=60, + check=False, + ) + + assert result.returncode == test_case.expected_returncode, result.stderr + assert "finalizing True" in result.stderr + assert "Waiting up to 5s for running scenario statements to stop" in result.stderr