From b2f0c4d21ea346e89c488a94751a0e1d38245e6f Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Sat, 17 Oct 2026 01:51:37 +0100 Subject: [PATCH 01/28] refactor: call native command helpers directly from the plan, query, scenario, seed and test commands Command bodies in plan_command, query_command, scenario_command, seed_command and sql_test_command (plus the rules custom evaluation and offline refactor compile) now call the Rust functions and construct the progress reporter pyclasses directly instead of looking them up on sqlbuild._native. The scenario command presentation tests now compile a real one-scenario DuckDB project instead of replacing native compile seams. --- .../build_progress/nested_progress.rs | 2 +- .../bindings/_helpers/cli/progress_text.rs | 4 +- .../_helpers/compile_command/target_writer.rs | 2 +- .../_helpers/compile_driver/analysis.rs | 2 +- .../_helpers/compile_driver/pipeline.rs | 2 +- .../compile_driver/pipeline_entries.rs | 2 +- .../bindings/_helpers/plan_command/plan.rs | 132 ++++++++------ .../bindings/_helpers/plan_command/rules.rs | 171 +++++++----------- .../_helpers/plan_output/plan_json.rs | 5 +- .../_helpers/plan_output/plan_text.rs | 2 +- .../_helpers/planner_entries/command_plans.rs | 6 +- .../bindings/_helpers/query_command/dag.rs | 63 +++---- .../_helpers/query_command/project.rs | 17 +- .../bindings/_helpers/query_command/query.rs | 27 +-- .../_helpers/refactor/offline_compile.rs | 83 +++++---- .../_helpers/result_output/documents.rs | 8 +- .../src/bindings/_helpers/rules/catalogue.rs | 2 +- .../_helpers/rules/custom_evaluation.rs | 48 ++--- .../_helpers/rules/custom_rule_cache.rs | 2 +- .../_helpers/rules/custom_rule_evidence.rs | 2 +- .../src/bindings/_helpers/rules/guidance.rs | 7 +- .../src/bindings/_helpers/rules/rule_run.rs | 2 +- .../_helpers/scenario_command/presentation.rs | 29 +-- .../_helpers/scenario_command/setup.rs | 126 ++++++------- .../scenario_command/snapshot_sync.rs | 66 +++---- .../_helpers/scenario_command/test_command.rs | 68 +++---- .../bindings/_helpers/seed_command/command.rs | 160 ++++++++-------- .../_helpers/sql_test_command/test_command.rs | 156 +++++++++------- .../cli/commands/main/execution/helpers.py | 79 ++++---- .../main/execution/test_scenario_command.py | 27 +-- .../cli/commands/main/scope/test_scope.py | 2 - 31 files changed, 650 insertions(+), 654 deletions(-) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/build_progress/nested_progress.rs b/crates/sqlbuild-python/src/bindings/_helpers/build_progress/nested_progress.rs index a5c341cf17..89cd2617dd 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_progress/nested_progress.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_progress/nested_progress.rs @@ -190,7 +190,7 @@ impl NestedCommandProgressCallbacks { impl NestedCommandProgressCallbacks { #[new] #[pyo3(signature = (**options))] - fn new(py: Python<'_>, options: Option>) -> PyResult { + pub(crate) fn new(py: Python<'_>, options: Option>) -> PyResult { let arguments = KeywordArguments::read( py, "NestedCommandProgressCallbacks", 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 33f9dc3f11..b40b095611 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cli/progress_text.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cli/progress_text.rs @@ -57,7 +57,7 @@ pub(crate) fn write_execution_header( /// Format a comparison result name as a user-facing expectation name. #[pyfunction] -fn format_expectation_name(model_name: &str) -> String { +pub(crate) fn format_expectation_name(model_name: &str) -> String { format_name(model_name) } @@ -95,7 +95,7 @@ pub(crate) fn expectation_step(step: &Bound<'_, PyAny>) -> PyResult) -> PyResult { +pub(crate) fn format_expectation_detail(step_result: &Bound<'_, PyAny>) -> PyResult { Ok(format_detail(&expectation_step(step_result)?)) } 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 9bd1f35ce2..d856b2ccb4 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 @@ -750,7 +750,7 @@ fn write_compile_target<'py>( /// Plan uncached SQL tests and report their errors without writing artifacts or cache. #[pyfunction] #[pyo3(signature = (**options))] -fn static_sql_test_planning_diagnostics<'py>( +pub(crate) fn static_sql_test_planning_diagnostics<'py>( py: Python<'py>, options: Option>, ) -> PyResult> { 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 bafd15f163..df662845d9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/analysis.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/analysis.rs @@ -132,7 +132,7 @@ fn any_error(diagnostics: &Bound<'_, PyAny>) -> PyResult { /// Discover, compile and validate the project into one `CompileAnalysis`. #[pyfunction] #[pyo3(signature = (**options))] -fn analyze_compile_project<'py>( +pub(crate) fn analyze_compile_project<'py>( py: Python<'py>, options: Option>, ) -> PyResult> { 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 b5de764392..adac9145ba 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/pipeline.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/pipeline.rs @@ -209,7 +209,7 @@ fn reject_compile_errors( /// Run compile inputs, assembly, planning, and manifest generation. #[pyfunction] #[pyo3(signature = (**options))] -fn run_compile_pipeline<'py>( +pub(crate) fn run_compile_pipeline<'py>( py: Python<'py>, options: Option>, ) -> PyResult> { 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 4a3a3da6c6..6ea8a42d00 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 @@ -172,7 +172,7 @@ pub(crate) fn build_static_pipeline_result<'py>( /// Compile a project for consumers that construct their own focused plans. #[pyfunction] #[pyo3(signature = (**options))] -fn run_compile_only_pipeline<'py>( +pub(crate) fn run_compile_only_pipeline<'py>( py: Python<'py>, options: Option>, ) -> PyResult> { 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 35517b405f..b8f276b628 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/plan_command/plan.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/plan_command/plan.rs @@ -7,16 +7,25 @@ use pyo3::types::{PyBool, PyDict, PyDictMethods}; use pyo3::wrap_pyfunction; use crate::bindings::_helpers::build_command::support::keyword_relay; +use crate::bindings::_helpers::cli::progress_reporters::{ + ConnectionProgressReporter, PlanningProgressReporter, +}; 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::command_runtime::preview_target::validate_preview_target; +use crate::bindings::_helpers::compile_attachment::effective_config::build_effective_settings_config; use crate::bindings::_helpers::compile_driver::discovery::discover_project_inputs; +use crate::bindings::_helpers::compile_driver::pipeline::run_compile_pipeline; use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::dbt_interop::reference_resolver::resolve_external_sql_reference_resolver; use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::plan_output::plan_json::format_plan_json; +use crate::bindings::_helpers::plan_output::plan_text::format_plan; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::records::registry; const COMMAND_NAME: &str = "plan"; @@ -76,11 +85,12 @@ fn resolve_invocation<'py>(request: &Bound<'py, PyAny>) -> PyResult = PyDict::new(py); - preview.set_item("discovered_inputs", &discovered)?; - preview.set_item("as_target", native_field(request, "as_target")?)?; - preview.set_item("command_name", COMMAND_NAME)?; - registry::native(py, NativeAttr::ValidatePreviewTarget)?.call((), Some(&preview))?; + validate_preview_target( + py, + discovered.clone(), + native_field(request, "as_target")?.extract()?, + COMMAND_NAME.to_owned(), + )?; let context: Bound<'py, PyAny> = resolve_adapter_connection_context( &discovered, &project_dir, @@ -97,28 +107,29 @@ fn resolve_invocation<'py>(request: &Bound<'py, PyAny>) -> PyResult = py.import("sys")? .getattr(if json_output { "stderr" } else { "stdout" })?; - let connection_progress: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, - &[ - ("adapter_name", native_field(&context, "adapter_name")?), - ("stream", stream.clone()), - ("use_color", flag(py, use_color)), - ], - )?; - let planning_progress: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::PlanningProgressReporter)?, - &[ - ("stream", stream.clone()), - ("use_color", flag(py, use_color)), - ], - )?; + let connection_progress: Bound<'py, PyAny> = Bound::new( + py, + ConnectionProgressReporter::new( + py, + Some(keywords( + py, + &[ + ("adapter_name", native_field(&context, "adapter_name")?), + ("stream", stream.clone()), + ("use_color", flag(py, use_color)), + ], + )?), + )?, + )? + .into_any(); + let planning_progress: Bound<'py, PyAny> = + Bound::new(py, PlanningProgressReporter::new(py, &stream, use_color)?)?.into_any(); let requested_load: Bound<'py, PyAny> = native_field(request, "load_sources")?; let load_sources: Bound<'py, PyAny> = if requested_load.is_none() { - call_with( - ®istry::native(py, NativeAttr::BuildEffectiveSettingsConfig)?, - &[("discovered_inputs", discovered.clone())], + native_field( + &build_effective_settings_config(&discovered)?, + "auto_load_sources", )? - .getattr("auto_load_sources")? } else { requested_load }; @@ -172,12 +183,9 @@ fn compile_pipeline<'py>( invocation: &PlanInvocation<'py>, ) -> PyResult> { let py: Python<'py> = request.py(); - let resolver: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::ResolveExternalSqlReferenceResolver)?, - &[ - ("project_dir", invocation.project_dir.clone()), - ("discovered_inputs", invocation.discovered.clone()), - ], + let resolver: Bound<'py, PyAny> = resolve_external_sql_reference_resolver( + invocation.project_dir.clone(), + invocation.discovered.clone(), )?; let as_target: Bound<'py, PyAny> = native_field(request, "as_target")?; if !as_target.is_none() { @@ -215,14 +223,17 @@ fn compile_pipeline<'py>( options.set_item("record_migration_fingerprints", false)?; let pipeline_options: Bound<'py, PyAny> = registry::class(py, RecordId::CompilePipelineOptions)?.call((), Some(&options))?; - call_with( - ®istry::native(py, NativeAttr::RunCompilePipeline)?, - &[ - ("discovered_inputs", invocation.discovered.clone()), - ("adapter", native_field(&invocation.context, "adapter")?), - ("options", pipeline_options), - ("hooks", connection_hooks(invocation)?), - ], + run_compile_pipeline( + py, + Some(keywords( + py, + &[ + ("discovered_inputs", invocation.discovered.clone()), + ("adapter", native_field(&invocation.context, "adapter")?), + ("options", pipeline_options), + ("hooks", connection_hooks(invocation)?), + ], + )?), ) } @@ -235,9 +246,12 @@ fn write_output( let plan: Bound<'_, PyAny> = native_field(pipeline, "plan_output")?; let python_entries: Bound<'_, PyAny> = native_field(pipeline, "python_plan_entries")?; if native_field(request, "json_output")?.is_truthy()? { - let text: Bound<'_, PyAny> = call_with( - ®istry::native(py, NativeAttr::FormatPlanJson)?, - &[("plan", plan), ("python_plan_entries", python_entries)], + let text: String = format_plan_json( + py, + Some(keywords( + py, + &[("plan", plan), ("python_plan_entries", python_entries)], + )?), )?; imported(py, "builtins", "print")?.call1((text,))?; return Ok(()); @@ -251,22 +265,24 @@ fn write_output( Some(DEFAULT_SECTION_ENTRIES) }, )?; - let text: String = call_with( - ®istry::native(py, NativeAttr::FormatPlan)?, - &[ - ("plan", plan), - ("full_refresh", native_field(request, "full_refresh")?), - ("use_color", flag(py, invocation.use_color)), - ( - "display_options", - registry::class(py, RecordId::DisplayOptions)?.call((), Some(&display))?, - ), - ("python_plan_entries", python_entries), - ("include_direct_freshness_diagnostics", flag(py, false)), - ("include_completion", flag(py, true)), - ], - )? - .extract()?; + let text: String = format_plan( + py, + Some(keywords( + py, + &[ + ("plan", plan), + ("full_refresh", native_field(request, "full_refresh")?), + ("use_color", flag(py, invocation.use_color)), + ( + "display_options", + registry::class(py, RecordId::DisplayOptions)?.call((), Some(&display))?, + ), + ("python_plan_entries", python_entries), + ("include_direct_freshness_diagnostics", flag(py, false)), + ("include_completion", flag(py, true)), + ], + )?), + )?; print_line(py, &format!("\n{text}")) } 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 b5a3ecbde1..f51397791d 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,22 @@ use pyo3::wrap_pyfunction; use crate::bindings::_helpers::command_runtime::adapters::resolve_cli_adapter; use crate::bindings::_helpers::compile_driver::discovery::discover_project_inputs; +use crate::bindings::_helpers::compile_driver::project::build_project_graph; +use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::compile_driver::python_calls::keywords; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::contracts::spec_resolution::resolve_effective_adapter_name; +use crate::bindings::_helpers::planner_selection::selectors::select_project_graph; +use crate::bindings::_helpers::planner_selection::unit_tests::reject_unit_test_selectors; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::rules::catalogue::build_rule_catalogue; +use crate::bindings::_helpers::rules::guidance::{format_rule, install_rules_skills}; +use crate::bindings::_helpers::rules::results::{ + format_rules_result, format_skipped_type_proof_rules, +}; +use crate::bindings::_helpers::rules::rule_run::run_rules; +use crate::bindings::_helpers::rules::rules_config::load_rules_config; const LIST_ACTION: &str = "list"; const SHOW_ACTION: &str = "show"; @@ -52,15 +63,7 @@ fn run_skills( ) -> PyResult { let py: Python<'_> = request.py(); let check: bool = native_field(request, "skills_check")?.is_truthy()?; - let fresh: bool = call_with( - ®istry::native(py, NativeAttr::InstallRulesSkills)?, - &[ - ("config", config.clone()), - ("project_dir", project_dir.clone()), - ("check", native_field(request, "skills_check")?), - ], - )? - .is_truthy()?; + let fresh: bool = install_rules_skills(config, project_dir, check)?; if !check { print_line(py, "Installed Rules skills", false)?; return Ok(0); @@ -130,11 +133,7 @@ fn show_rule( for rule in catalogue.try_iter()? { let rule: Bound<'_, PyAny> = rule?; if native_field(&rule, "code")?.extract::()? == selector { - let text: String = call_with( - ®istry::native(py, NativeAttr::FormatRule)?, - &[("rule", rule), ("config", config.clone())], - )? - .extract()?; + let text: String = format_rule(&rule, Some(config.clone()))?; print_line(py, &text, false)?; return Ok(0); } @@ -162,40 +161,23 @@ fn rule_inputs<'py>( )?; let select: Bound<'py, PyAny> = native_field(request, "select")?; let exclude: Bound<'py, PyAny> = native_field(request, "exclude")?; - call_with( - ®istry::native(py, NativeAttr::RejectUnitTestSelectors)?, - &[ - ("select", select.clone()), - ("exclude", exclude.clone()), - ("discovered_inputs", discovered.clone()), - ], - )?; - let adapter_name: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, - &[ - ( - "project_config", - native_field(&discovered, "project_config")?, - ), - ("local_config", native_field(&discovered, "local_config")?), - ], + reject_unit_test_selectors(&select, &exclude, &discovered)?; + let adapter_name: Bound<'py, PyAny> = resolve_effective_adapter_name( + &native_field(&discovered, "project_config")?, + &native_field(&discovered, "local_config")?, )?; let adapter: Bound<'py, PyAny> = resolve_cli_adapter(&adapter_name, project_dir)?; - let graph: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::BuildProjectGraph)?, - &[ - ("discovered_inputs", discovered.clone()), - ("adapter", adapter.clone()), - ], - )?; - let selected: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::SelectProjectGraph)?, - &[ - ("graph", graph.clone()), - ("select", select), - ("exclude", exclude), - ], + let graph: Bound<'py, PyAny> = build_project_graph( + py, + Some(keywords( + py, + &[ + ("discovered_inputs", discovered.clone()), + ("adapter", adapter.clone()), + ], + )?), )?; + let selected: Bound<'py, PyAny> = select_project_graph(&graph, &select, &exclude)?.into_any(); Ok(RuleInputs { discovered, adapter, @@ -235,56 +217,50 @@ fn run_rule_selection( replaced.set_item("select", PyTuple::new(py, [selector])?)?; replaced.set_item("ignore", PyTuple::empty(py))?; let dialect: Bound<'_, PyAny> = adapter.call_method0("sql_analysis_dialect")?; - let result: Bound<'_, PyAny> = call_with( - ®istry::native(py, NativeAttr::RunRules)?, - &[ - ("graph", graph), - ("discovered_inputs", discovered), - ( - "config", - imported(py, "dataclasses", "replace")?.call((config,), Some(&replaced))?, - ), - ("project_dir", project_dir.clone()), - ( - "dialect", - if dialect.is_truthy()? { - dialect - } else { - pyo3::types::PyString::new(py, GENERIC_DIALECT).into_any() - }, - ), - ( - "selected_keys", - if narrowed { - selected - } else { - py.None().into_bound(py) - }, - ), - ], - )?; - let note: Bound<'_, PyAny> = call_with( - ®istry::native(py, NativeAttr::FormatSkippedTypeProofRules)?, - &[("codes", native_field(&result, "skipped_type_proof_rules")?)], + let result: Bound<'_, PyAny> = run_rules( + py, + Some(keywords( + py, + &[ + ("graph", graph), + ("discovered_inputs", discovered), + ( + "config", + imported(py, "dataclasses", "replace")?.call((config,), Some(&replaced))?, + ), + ("project_dir", project_dir.clone()), + ( + "dialect", + if dialect.is_truthy()? { + dialect + } else { + pyo3::types::PyString::new(py, GENERIC_DIALECT).into_any() + }, + ), + ( + "selected_keys", + if narrowed { + selected + } else { + py.None().into_bound(py) + }, + ), + ], + )?), )?; - if !note.is_none() { - print_line(py, &format!("note: {}", note.str()?), true)?; + if let Some(note) = format_skipped_type_proof_rules( + native_field(&result, "skipped_type_proof_rules")?.extract()?, + ) { + print_line(py, &format!("note: {note}"), true)?; } let fields: Bound<'_, PyDict> = PyDict::new(py); for field in RESULT_FIELDS { fields.set_item(field, result.getattr(field)?)?; } - let text: String = call_with( - ®istry::native(py, NativeAttr::FormatRulesResult)?, - &[ - ( - "result", - registry::class(py, RecordId::RulesResult)?.call((), Some(&fields))?, - ), - ("json_output", native_field(request, "json_output")?), - ], - )? - .extract()?; + let text: String = format_rules_result( + ®istry::class(py, RecordId::RulesResult)?.call((), Some(&fields))?, + native_field(request, "json_output")?.is_truthy()?, + )?; print_line(py, &text, false)?; let findings: usize = native_field(&result, "findings")?.len()?; if findings > 0 { @@ -311,21 +287,12 @@ fn run_rules_command(request: &Bound<'_, PyAny>) -> PyResult { requested_dir } .call_method0("resolve")?; - let config: Bound<'_, PyAny> = call_with( - ®istry::native(py, NativeAttr::LoadRulesConfig)?, - &[("project_dir", project_dir.clone())], - )?; + let config: Bound<'_, PyAny> = load_rules_config(py, project_dir.extract()?)?.into_bound(py); let action: String = native_field(request, "action")?.extract()?; if action == SKILLS_ACTION { return run_skills(request, &config, &project_dir); } - let catalogue: Bound<'_, PyAny> = call_with( - ®istry::native(py, NativeAttr::BuildRuleCatalogue)?, - &[ - ("config", config.clone()), - ("project_dir", project_dir.clone()), - ], - )?; + let catalogue: Bound<'_, PyAny> = build_rule_catalogue(&config, &project_dir, true)?.into_any(); if action == LIST_ACTION { return list_rules( &catalogue, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/plan_output/plan_json.rs b/crates/sqlbuild-python/src/bindings/_helpers/plan_output/plan_json.rs index f9144957f4..eea3606772 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/plan_output/plan_json.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/plan_output/plan_json.rs @@ -706,7 +706,10 @@ fn table_type_json<'py>(plan: &Bound<'py, PyAny>) -> PyResult> /// The plan as one JSON document, `json.dumps(indent=2)`. #[pyfunction] #[pyo3(signature = (**options))] -fn format_plan_json(py: Python<'_>, options: Option>) -> PyResult { +pub(crate) fn format_plan_json( + py: Python<'_>, + options: Option>, +) -> PyResult { let arguments: KeywordArguments<'_> = KeywordArguments::read( py, "format_plan_json", 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 a18c46709f..8380e47bf2 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 @@ -488,7 +488,7 @@ fn report(arguments: &KeywordArguments<'_>) -> PyResult { /// The text plan grouped by reason with inline detail, optionally closed by its completion line. #[pyfunction] #[pyo3(signature = (**options))] -fn format_plan(py: Python<'_>, options: Option>) -> PyResult { +pub(crate) fn format_plan(py: Python<'_>, options: Option>) -> PyResult { let arguments: KeywordArguments<'_> = KeywordArguments::read(py, "format_plan", options, PLAN_OPTIONS)?; let report: PlanTextReport = report(&arguments)?; 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 52f30a7ad8..fcced5ca07 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 @@ -192,7 +192,7 @@ fn build_audit_command_plan<'py>( /// Project selected SQL tests from canonical static state. #[pyfunction] #[pyo3(signature = (**kwargs))] -fn build_test_command_plan<'py>( +pub(crate) fn build_test_command_plan<'py>( py: Python<'py>, kwargs: Option>, ) -> PyResult> { @@ -283,7 +283,7 @@ fn build_test_command_plan<'py>( /// Project selected direct seed work and canonical identities. #[pyfunction] #[pyo3(signature = (*, project, scope, relations, fingerprints = None))] -fn build_seed_command_plan<'py>( +pub(crate) fn build_seed_command_plan<'py>( project: &Bound<'py, PyAny>, scope: &Bound<'py, PyAny>, relations: &Bound<'py, PyAny>, @@ -362,7 +362,7 @@ type Target = (Option, String); /// Read only fingerprint state required by selected direct seeds. #[pyfunction] #[pyo3(signature = (*, adapter, connection, seeds))] -fn read_selected_seed_fingerprints<'py>( +pub(crate) fn read_selected_seed_fingerprints<'py>( adapter: &Bound<'py, PyAny>, connection: &Bound<'py, PyAny>, seeds: &Bound<'py, PyAny>, 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 f1f72fbcec..8576553bc6 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/query_command/dag.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/query_command/dag.rs @@ -6,12 +6,14 @@ 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}; +use crate::bindings::_helpers::compile_driver::project::build_project_graph; +use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; +use crate::bindings::_helpers::compile_driver::python_calls::keywords; +use crate::bindings::_helpers::dag_artifact::artifact::build_dag_json; +use crate::bindings::_helpers::dbt_interop::reference_resolver::resolve_external_sql_reference_resolver; use crate::bindings::_helpers::graph::python_node_graph::python_node_graph; use crate::bindings::_helpers::query_command::project::{ProjectAdapter, project_adapter}; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::NativeAttr; -use crate::bindings::_helpers::records::registry; fn array_length(document: &serde_json::Value, key: &str) -> PyResult { document @@ -43,39 +45,32 @@ fn run_dag_command<'py>(py: Python<'py>, options: Option>) -> discovered, adapter, } = project_adapter(py, project_dir)?; - let resolver: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::ResolveExternalSqlReferenceResolver)?, - &[ - ("project_dir", project_dir), - ("discovered_inputs", discovered.clone()), - ], - )?; - let graph: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::BuildProjectGraph)?, - &[ - ("discovered_inputs", discovered.clone()), - ("adapter", adapter), - ( - "no_sql_validation", - PyBool::new(py, no_sql_validation).to_owned().into_any(), - ), - ("cli_vars", cli_vars), - ("external_sql_reference_resolver", resolver), - ], + let resolver: Bound<'py, PyAny> = + resolve_external_sql_reference_resolver(project_dir, discovered.clone())?; + let graph: Bound<'py, PyAny> = build_project_graph( + py, + Some(keywords( + py, + &[ + ("discovered_inputs", discovered.clone()), + ("adapter", adapter), + ( + "no_sql_validation", + PyBool::new(py, no_sql_validation).to_owned().into_any(), + ), + ("cli_vars", cli_vars), + ("external_sql_reference_resolver", resolver), + ], + )?), )?; let python_graph: Bound<'py, PyAny> = python_node_graph(&discovered)?; - let dag_json: String = call_with( - ®istry::native(py, NativeAttr::BuildDagJson)?, - &[ - ("graph", graph), - ( - "project_name", - native_field(&discovered, "project_config")?.getattr("name")?, - ), - ("python_graph", python_graph), - ], - )? - .extract()?; + let dag_json: String = build_dag_json( + &graph, + &native_field(&discovered, "project_config")? + .getattr("name")? + .extract::()?, + Some(&python_graph), + )?; let rendered: String = if json_output { format!("{dag_json}\n") } else { 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 4bd16132a2..eeaaa42ae5 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/query_command/project.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/query_command/project.rs @@ -4,11 +4,10 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use crate::bindings::_helpers::command_runtime::adapters::resolve_cli_adapter; use crate::bindings::_helpers::compile_driver::discovery::discover_project_inputs; +use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::compile_driver::python_calls::keywords; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::contracts::spec_resolution::resolve_effective_adapter_name; use crate::bindings::_helpers::records::fields::field as native_field; -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> { @@ -29,15 +28,9 @@ pub(crate) fn project_adapter<'py>( py, Some(keywords(py, &[("project_dir", project_dir.clone())])?), )?; - let adapter_name: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, - &[ - ( - "project_config", - native_field(&discovered, "project_config")?, - ), - ("local_config", native_field(&discovered, "local_config")?), - ], + let adapter_name: Bound<'py, PyAny> = resolve_effective_adapter_name( + &native_field(&discovered, "project_config")?, + &native_field(&discovered, "local_config")?, )?; let adapter: Bound<'py, PyAny> = resolve_cli_adapter(&adapter_name, &project_dir)?; Ok(ProjectAdapter { 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 64d6817660..f566c1853a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/query_command/query.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/query_command/query.rs @@ -13,6 +13,8 @@ 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::command_runtime::connection::resolve_project_connection_config; +use crate::bindings::_helpers::compile_driver::python_calls::keywords; 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; @@ -21,7 +23,7 @@ use crate::bindings::_helpers::query_command::query_values::{ csv_text, display_text, json_cell, python_json_rows, strict_width, }; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr}; +use crate::bindings::_helpers::records::ids::ErrorId; use crate::bindings::_helpers::records::registry; const LONG_FORMAT: &str = "long"; @@ -146,16 +148,19 @@ fn run_sql<'py>( limit: Option, ) -> PyResult> { let py: Python<'py> = sql.py(); - let connection_config: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::ResolveProjectConnectionConfig)?, - &[ - ("discovered_inputs", project.discovered.clone()), - ("project_dir", project.project_dir.clone()), - ( - "selected_target", - selected_target.unwrap_or_else(|| py.None().into_bound(py)), - ), - ], + let connection_config: Bound<'py, PyAny> = resolve_project_connection_config( + py, + Some(keywords( + py, + &[ + ("discovered_inputs", project.discovered.clone()), + ("project_dir", project.project_dir.clone()), + ( + "selected_target", + selected_target.unwrap_or_else(|| py.None().into_bound(py)), + ), + ], + )?), )?; let connection: Bound<'py, PyAny> = project .adapter 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 48394339ee..93d0002d43 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/refactor/offline_compile.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/refactor/offline_compile.rs @@ -5,11 +5,14 @@ use std::path::Path; use pyo3::IntoPyObject; use pyo3::exceptions::PyValueError; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python}; -use pyo3::types::{PyBool, PyString}; +use pyo3::types::{PyBool, PyString, PyTuple}; +use crate::bindings::_helpers::compile_command::target_writer::static_sql_test_planning_diagnostics; +use crate::bindings::_helpers::compile_driver::analysis::analyze_compile_project; use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; const DEFAULT_ERROR_CODE: &str = "E001"; @@ -108,27 +111,30 @@ fn analyzed<'py>( ®istry::class(py, RecordId::CompileAnalysisSelection)?, &[("no_cache", PyBool::new(py, no_cache).to_owned().into_any())], )?; - call_with( - ®istry::native(py, NativeAttr::AnalyzeCompileProject)?, - &[ - ("project_dir", project_dir.into_pyobject(py)?.into_any()), - ( - "no_sql_validation", - PyBool::new(py, false).to_owned().into_any(), - ), - ("selected_target", py.None().into_bound(py)), - ( - "lineage_mode", - registry::class(py, RecordId::CompileLineageMode)?.getattr("NONE")?, - ), - ("cli_vars", py.None().into_bound(py)), - ( - "profile_flags", - registry::class(py, RecordId::CompileProfileFlags)?.call0()?, - ), - ("analysis_selection", selection), - ("status", py.None().into_bound(py)), - ], + analyze_compile_project( + py, + Some(keywords( + py, + &[ + ("project_dir", project_dir.into_pyobject(py)?.into_any()), + ( + "no_sql_validation", + PyBool::new(py, false).to_owned().into_any(), + ), + ("selected_target", py.None().into_bound(py)), + ( + "lineage_mode", + registry::class(py, RecordId::CompileLineageMode)?.getattr("NONE")?, + ), + ("cli_vars", py.None().into_bound(py)), + ( + "profile_flags", + registry::class(py, RecordId::CompileProfileFlags)?.call0()?, + ), + ("analysis_selection", selection), + ("status", py.None().into_bound(py)), + ], + )?), ) } @@ -154,22 +160,25 @@ pub(crate) fn compile_for_refactor<'py>( let mut errors: Vec> = error_diagnostics(native_field(&analysis, "diagnostics")?)?; if errors.is_empty() { - let planned: PyResult> = call_with( - ®istry::native(py, NativeAttr::StaticSqlTestPlanningDiagnostics)?, - &[ - ( - "target_dir", - project_dir.join("target").into_pyobject(py)?.into_any(), - ), - ("adapter", native_field(&analysis, "adapter")?), - ( - "project", - native_field(&analysis, "graph")?.getattr("project")?, - ), - ], + let planned: PyResult> = static_sql_test_planning_diagnostics( + py, + Some(keywords( + py, + &[ + ( + "target_dir", + project_dir.join("target").into_pyobject(py)?.into_any(), + ), + ("adapter", native_field(&analysis, "adapter")?), + ( + "project", + native_field(&analysis, "graph")?.getattr("project")?, + ), + ], + )?), ); match planned { - Ok(diagnostics) => errors.extend(error_diagnostics(diagnostics)?), + Ok(diagnostics) => errors.extend(error_diagnostics(diagnostics.into_any())?), Err(error) if matches_any_class( py, 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 e27d6af154..e33dbe78eb 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/result_output/documents.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/result_output/documents.rs @@ -381,7 +381,7 @@ fn build_json<'py>( /// Format seed command execution results as JSON. #[pyfunction] #[pyo3(signature = (*, results, plan))] -fn format_seed_execution_json<'py>( +pub(crate) fn format_seed_execution_json<'py>( py: Python<'py>, results: Bound<'py, PyAny>, plan: Bound<'py, PyAny>, @@ -487,7 +487,7 @@ fn format_clone_execution_json<'py>( /// Format test command execution results as JSON. #[pyfunction] #[pyo3(signature = (*, results))] -fn format_test_execution_json<'py>( +pub(crate) fn format_test_execution_json<'py>( py: Python<'py>, results: Bound<'py, PyAny>, ) -> PyResult { @@ -604,7 +604,7 @@ fn scenario_failed(result: &Bound<'_, PyAny>, local: bool) -> PyResult { /// Format scenario test command execution results as JSON. #[pyfunction] #[pyo3(signature = (**options))] -fn format_scenario_execution_json<'py>( +pub(crate) fn format_scenario_execution_json<'py>( py: Python<'py>, options: Option>, ) -> PyResult { @@ -694,7 +694,7 @@ fn format_scenario_execution_json<'py>( /// Format scenario snapshot sync/refresh execution results as JSON. #[pyfunction] #[pyo3(signature = (**options))] -fn format_scenario_snapshot_execution_json<'py>( +pub(crate) fn format_scenario_snapshot_execution_json<'py>( py: Python<'py>, options: Option>, ) -> PyResult { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/catalogue.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/catalogue.rs index 7eeb1913e9..bb581b4ea5 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/catalogue.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/catalogue.rs @@ -278,7 +278,7 @@ pub(crate) fn verify_custom_rules<'py>( /// The validated built-in and custom rule catalogue sorted by code. #[pyfunction] #[pyo3(signature = (*, config, project_dir, include_custom = true))] -fn build_rule_catalogue<'py>( +pub(crate) fn build_rule_catalogue<'py>( config: &Bound<'py, PyAny>, project_dir: &Bound<'py, PyAny>, include_custom: bool, 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 41e812d345..85147f2165 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_evaluation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_evaluation.rs @@ -16,6 +16,7 @@ use sqlbuild_rules::custom_rule_cache::models::CustomEvaluation; 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::keywords; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; @@ -27,8 +28,12 @@ use crate::bindings::_helpers::rules::custom_hosts::{ HostCancel, HostRequest, host_payload_project, run_custom_hosts, }; use crate::bindings::_helpers::rules::custom_rule_cache::CustomRuleCache; +use crate::bindings::_helpers::rules::custom_rule_cache::custom_rule_identity; +use crate::bindings::_helpers::rules::custom_rule_evidence::custom_rule_implementation_fingerprint; +use crate::bindings::_helpers::rules::custom_rule_evidence::custom_rule_import_closure; use crate::bindings::_helpers::rules::fact_encoder::{Encoded, reproducible}; use crate::bindings::_helpers::rules::fact_replay::{FactReplay, is_tree_fact}; +use crate::bindings::_helpers::rules::rule_run::rules_bulk_cache_path; const PROJECT_SUBJECT: &str = "project"; const MODEL_SUBJECT: &str = "model"; @@ -107,19 +112,10 @@ pub(crate) fn evaluate_custom_rules_cached<'py>( rows.push((code.clone(), identity, subject_value(rule)?)); } let cache_path: Option = if enabled { - Some( - call_with( - ®istry::native(py, NativeAttr::RulesBulkCachePath)?, - &[ - ("project_dir", run.project_dir.clone()), - ( - "file_name", - pyo3::types::PyString::new(py, CUSTOM_RULES_CACHE_FILE).into_any(), - ), - ], - )? - .extract()?, - ) + Some(rules_bulk_cache_path( + run.project_dir.extract()?, + CUSTOM_RULES_CACHE_FILE, + )) } else { None }; @@ -352,26 +348,15 @@ fn rule_identity( let fingerprint: Bound<'_, PyAny> = match fingerprint { Some(fingerprint) => fingerprint.into_bound_py_any(py)?, None => { - let closure: Bound<'_, PyAny> = call_with( - ®istry::native(py, NativeAttr::CustomRuleImportClosure)?, - &[ - ("rule", rule.clone()), - ("project_dir", run.project_dir.clone()), - ], - )?; - call_with( - ®istry::native(py, NativeAttr::CustomRuleImplementationFingerprint)?, - &[ - ("rule", rule.clone()), - ("project_dir", run.project_dir.clone()), - ("import_closure", closure), - ], - )? + let closure: Bound<'_, PyAny> = + custom_rule_import_closure(rule, run.project_dir)?.into_any(); + custom_rule_implementation_fingerprint(rule, run.project_dir, Some(&closure))? + .into_bound_py_any(py)? } }; let subject: Option = subject_value(rule)?; - call_with( - ®istry::native(py, NativeAttr::CustomRuleIdentity)?, + custom_rule_identity(Some(&keywords( + py, &[ ( "code_identity", @@ -395,8 +380,7 @@ fn rule_identity( run.project_dir.call_method0("resolve")?.str()?.into_any(), ), ], - )? - .extract() + )?)) } /// This thread's CPU plus that of finished child processes, which run the rule hosts. 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 a1afcc0327..cc5d761e9f 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 @@ -30,7 +30,7 @@ use crate::bindings::_helpers::rules::findings::{FindingClasses, invalid_finding /// One custom rule's cache identity from its metadata, source fingerprint and settings. #[pyfunction] #[pyo3(signature = (**request))] -fn custom_rule_identity(request: Option<&Bound<'_, PyDict>>) -> PyResult { +pub(crate) fn custom_rule_identity(request: Option<&Bound<'_, PyDict>>) -> PyResult { let request = Request::new("custom_rule_identity", request)?; Ok(rule_identity(&RuleIdentityInput { code_identity: request.get("code_identity")?.extract()?, 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 d3dd9b1398..108536a0e1 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 @@ -59,7 +59,7 @@ fn closure_paths<'py>(py: Python<'py>, paths: Vec) -> PyResult( +pub(crate) fn custom_rule_import_closure<'py>( rule: &Bound<'py, PyAny>, project_dir: &Bound<'py, PyAny>, ) -> PyResult> { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/guidance.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/guidance.rs index da53ed57d0..061a2f62ce 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/guidance.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/guidance.rs @@ -92,7 +92,10 @@ fn rule_text(rule: &Bound<'_, PyAny>, config: Option<&Bound<'_, PyAny>>) -> PyRe /// `sqb rules show` text for one rule, with effective thresholds when a config is given. #[pyfunction] #[pyo3(signature = (*, rule, config = None))] -fn format_rule(rule: &Bound<'_, PyAny>, config: Option>) -> PyResult { +pub(crate) fn format_rule( + rule: &Bound<'_, PyAny>, + config: Option>, +) -> PyResult { let config: Option> = config.filter(|config| !config.is_none()); let native: Option = config.as_ref().map(native_rules_config).transpose()?; let text: RuleText = rule_text(rule, config.as_ref())?; @@ -186,7 +189,7 @@ fn lock_path(project_dir: &Path) -> PyResult { /// Install every owned rules skill target, or with `check` report whether all are fresh. #[pyfunction] #[pyo3(signature = (*, config, project_dir, check))] -fn install_rules_skills( +pub(crate) fn install_rules_skills( config: &Bound<'_, PyAny>, project_dir: &Bound<'_, PyAny>, check: bool, 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 3b3961fc8e..e8f70c1d5a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_run.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_run.rs @@ -141,7 +141,7 @@ pub(crate) fn run_rules<'py>( /// One bulk Rules cache file owned by the native compiler engine. #[pyfunction] #[pyo3(signature = (*, project_dir, file_name))] -fn rules_bulk_cache_path(project_dir: PathBuf, file_name: &str) -> PathBuf { +pub(crate) fn rules_bulk_cache_path(project_dir: PathBuf, file_name: &str) -> PathBuf { bulk_cache_path(&project_dir, file_name) } 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 99217f2c63..1d40c492fb 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/presentation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/presentation.rs @@ -13,12 +13,12 @@ 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::cli::progress_reporters::ConnectionProgressReporter; use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::records::fields::field as native_field; -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, }; @@ -161,15 +161,22 @@ pub(crate) fn connection_hooks<'py>( (adapter_name, style): (&str, CliStyle), ) -> PyResult> { let py: Python<'py> = stream.py(); - let reporter: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, - &[ - ("adapter_name", text(py, adapter_name)), - ("blank_line_after_complete", boolean(py, true)), - ("stream", stream.clone()), - ("use_color", boolean(py, style.use_color)), - ], - )?; + let reporter: Bound<'py, PyAny> = Bound::new( + py, + ConnectionProgressReporter::new( + py, + Some(keywords( + py, + &[ + ("adapter_name", text(py, adapter_name)), + ("blank_line_after_complete", boolean(py, true)), + ("stream", stream.clone()), + ("use_color", boolean(py, style.use_color)), + ], + )?), + )?, + )? + .into_any(); let relay: Bound<'py, ScenarioConnectionRelay> = Bound::new( py, ScenarioConnectionRelay { 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 3f5f1bb1fb..cd6065e701 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/setup.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/setup.rs @@ -20,16 +20,24 @@ use sqlbuild_core::scenario_command::models::{ SnapshotLimitInputs, SnapshotLimits, SqlAnalysisCommand, }; +use crate::bindings::_helpers::cli::progress_reporters::PlanningProgressReporter; +use crate::bindings::_helpers::cli::progress_text::write_execution_header; use crate::bindings::_helpers::command_runtime::adapters::resolve_cli_adapter; +use crate::bindings::_helpers::command_runtime::connection::resolve_project_connection_config; use crate::bindings::_helpers::compile_driver::discovery::discover_project_inputs; +use crate::bindings::_helpers::compile_driver::pipeline_entries::run_compile_only_pipeline; use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, within}; +use crate::bindings::_helpers::contracts::spec_resolution::resolve_effective_adapter_name; +use crate::bindings::_helpers::contracts::spec_resolution::resolve_effective_scenario_config; +use crate::bindings::_helpers::dbt_interop::reference_resolver::resolve_external_sql_reference_resolver; 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::planner_selection::unit_tests::reject_unit_test_selectors; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; const METADATA_MODULE: &str = "importlib.metadata"; @@ -122,14 +130,7 @@ pub(crate) fn discover_project<'py>( )?; let selectors: Bound<'py, PyAny> = native_field(request, "selectors")?; let exclude: Bound<'py, PyAny> = native_field(request, "exclude")?; - call_with( - ®istry::native(py, NativeAttr::RejectUnitTestSelectors)?, - &[ - ("select", selectors.clone()), - ("exclude", exclude.clone()), - ("discovered_inputs", discovered.clone()), - ], - )?; + reject_unit_test_selectors(&selectors.clone(), &exclude.clone(), &discovered.clone())?; let local_config: Bound<'py, PyAny> = native_field(&discovered, "local_config")?; let candidates: NamespaceCandidates = NamespaceCandidates { cli: native_field(request, "scenario_namespace")?.extract()?, @@ -187,19 +188,9 @@ pub(crate) fn require_sql_analysis( /// The adapter named by the project's effective configuration. pub(crate) fn project_adapter_name(project: &ScenarioProject<'_>) -> PyResult { - let py: Python<'_> = project.discovered.py(); - call_with( - ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, - &[ - ( - "project_config", - native_field(&project.discovered, "project_config")?, - ), - ( - "local_config", - native_field(&project.discovered, "local_config")?, - ), - ], + resolve_effective_adapter_name( + &native_field(&project.discovered, "project_config")?, + &native_field(&project.discovered, "local_config")?, )? .extract() } @@ -226,12 +217,15 @@ pub(crate) fn project_connection_config<'py>( project: &ScenarioProject<'py>, ) -> PyResult> { let py: Python<'py> = project.discovered.py(); - call_with( - ®istry::native(py, NativeAttr::ResolveProjectConnectionConfig)?, - &[ - ("discovered_inputs", project.discovered.clone()), - ("project_dir", project.project_dir.clone()), - ], + resolve_project_connection_config( + py, + Some(keywords( + py, + &[ + ("discovered_inputs", project.discovered.clone()), + ("project_dir", project.project_dir.clone()), + ], + )?), ) } @@ -251,24 +245,25 @@ pub(crate) fn write_headers<'py>( let py: Python<'py> = stream.py(); write(stream, &render_namespace_header(&project.namespace))?; let selectors: Vec = project.selectors.extract()?; - let planning: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::PlanningProgressReporter)?, - &[ - ("stream", stream.clone()), - ("use_color", boolean(py, style.use_color)), - ], - )?; + let planning: Bound<'py, PyAny> = Bound::new( + py, + PlanningProgressReporter::new(py, stream, style.use_color)?, + )? + .into_any(); write(stream, "\n")?; let target: Option = (!selectors.is_empty()).then(|| selectors.join(" ")); - call_with( - ®istry::native(py, NativeAttr::WriteExecutionHeader)?, - &[ - ("stream", stream.clone()), - ("command", text(py, command)), - ("target", optional_text(py, target.as_deref())), - ("concurrency", int(py, concurrency)), - ("use_color", boolean(py, style.use_color)), - ], + write_execution_header( + py, + Some(keywords( + py, + &[ + ("stream", stream.clone()), + ("command", text(py, command)), + ("target", optional_text(py, target.as_deref())), + ("concurrency", int(py, concurrency)), + ("use_color", boolean(py, style.use_color)), + ], + )?), )?; Ok(planning) } @@ -291,12 +286,9 @@ pub(crate) fn compile<'py>( planning, } = inputs; let py: Python<'py> = project.discovered.py(); - let resolver: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::ResolveExternalSqlReferenceResolver)?, - &[ - ("project_dir", project.project_dir.clone()), - ("discovered_inputs", project.discovered.clone()), - ], + let resolver: Bound<'py, PyAny> = resolve_external_sql_reference_resolver( + project.project_dir.clone(), + project.discovered.clone(), )?; let mut pairs: Vec<(&str, Bound<'py, PyAny>)> = options.to_vec(); pairs.push(("external_sql_reference_resolver", resolver)); @@ -312,14 +304,17 @@ pub(crate) fn compile<'py>( ], )?; within(&lifecycle, |_| { - call_with( - ®istry::native(py, NativeAttr::RunCompileOnlyPipeline)?, - &[ - ("discovered_inputs", project.discovered.clone()), - ("adapter", adapter.clone()), - ("options", options.clone()), - ("on_progress", native_field(planning, "on_progress")?), - ], + run_compile_only_pipeline( + py, + Some(keywords( + py, + &[ + ("discovered_inputs", project.discovered.clone()), + ("adapter", adapter.clone()), + ("options", options.clone()), + ("on_progress", native_field(planning, "on_progress")?), + ], + )?), ) }) } @@ -368,18 +363,9 @@ pub(crate) fn capture_limits<'py>( ) -> PyResult> { let py: Python<'py> = request.py(); let inputs: Bound<'py, PyAny> = native_field(request, "limit_inputs")?; - let configured: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::ResolveEffectiveScenarioConfig)?, - &[ - ( - "project_config", - native_field(&project.discovered, "project_config")?, - ), - ( - "local_config", - native_field(&project.discovered, "local_config")?, - ), - ], + let configured: Bound<'py, PyAny> = resolve_effective_scenario_config( + &native_field(&project.discovered, "project_config")?, + &native_field(&project.discovered, "local_config")?, )? .getattr("snapshot_limits")?; let limits: SnapshotLimits = resolve_snapshot_limits( 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 a4479105cd..a3222fce49 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 @@ -1,18 +1,20 @@ //! `sqb scenario test --local --sync-snapshots` and `--refresh`: capture stale snapshots first. use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; -use pyo3::types::{PyTuple, PyTupleMethods}; +use pyo3::types::{PyString, PyTuple, PyTupleMethods}; use sqlbuild_core::cli_text::main::format_summary_footer::format_summary_footer; 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::cli::progress_reporters::PlanningProgressReporter; use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::NativeAttr; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::result_output::documents::format_scenario_snapshot_execution_json; +use crate::bindings::_helpers::result_output::documents::write_execution_json_output; use crate::bindings::_helpers::scenario_command::conversions::succeeded; use crate::bindings::_helpers::scenario_command::presentation::{ CompletionKind, SectionStatus, begin_section, connection_hooks, progress_relay, @@ -38,28 +40,30 @@ impl LocalSync<'_> { namespace: &ScenarioNamespace, ) -> PyResult { let py: Python<'_> = request.py(); - let payload: Bound<'_, PyAny> = call_with( - ®istry::native(py, NativeAttr::FormatScenarioSnapshotExecutionJson)?, - &[ - ( - "run_namespace", - optional_text(py, namespace.value.as_deref()), - ), - ("namespace_source", text(py, &namespace.source)), - ("results", self.results.clone().into_any()), - ("refresh", boolean(py, self.refresh)), - ], - )?; - call_with( - ®istry::native(py, NativeAttr::WriteExecutionJsonOutput)?, - &[ - ("payload", payload), - ("json_output", native_field(request, "json_output")?), - ( - "json_output_path", - native_field(request, "json_output_path")?, - ), - ], + let payload: Bound<'_, PyAny> = PyString::new( + py, + &format_scenario_snapshot_execution_json( + py, + Some(keywords( + py, + &[ + ( + "run_namespace", + optional_text(py, namespace.value.as_deref()), + ), + ("namespace_source", text(py, &namespace.source)), + ("results", self.results.clone().into_any()), + ("refresh", boolean(py, self.refresh)), + ], + )?), + )?, + ) + .into_any(); + write_execution_json_output( + py, + payload, + native_field(request, "json_output")?.extract()?, + native_field(request, "json_output_path")?, )?; Ok(self.exit_code) } @@ -113,13 +117,11 @@ pub(crate) fn sync_local_snapshots<'py>( refresh, }); } - let planning: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::PlanningProgressReporter)?, - &[ - ("stream", run.stream.clone()), - ("use_color", boolean(py, run.style.use_color)), - ], - )?; + let planning: Bound<'py, PyAny> = Bound::new( + py, + PlanningProgressReporter::new(py, &run.stream, run.style.use_color)?, + )? + .into_any(); let compiled: PyResult> = compile( &run.project, CompileInputs { 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 624d65d4bd..7abb03f13f 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 @@ -4,7 +4,7 @@ use std::path::PathBuf; use std::time::Instant; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; -use pyo3::types::{PyDict, PyDictMethods, PyTuple, PyTupleMethods}; +use pyo3::types::{PyDict, PyDictMethods, PyString, PyTuple, PyTupleMethods}; use pyo3::{pyfunction, wrap_pyfunction}; use sqlbuild_core::cli_text::main::format_summary_footer::format_summary_footer; use sqlbuild_core::cli_text::models::{CliStyle, SummaryCount}; @@ -13,11 +13,13 @@ use sqlbuild_core::scenario_command::models::{ ScenarioNamespace, ScenarioTestFlags, SqlAnalysisCommand, }; +use crate::bindings::_helpers::compile_attachment::effective_config::build_effective_settings_config; use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::compile_driver::python_calls::keywords; 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::result_output::documents::format_scenario_execution_json; +use crate::bindings::_helpers::result_output::documents::write_execution_json_output; use crate::bindings::_helpers::scenario_command::conversions::succeeded; use crate::bindings::_helpers::scenario_command::presentation::{ CompletionKind, SectionStatus, activity, begin_section, connection_hooks, progress_relay, @@ -95,12 +97,9 @@ fn prepare<'py>(py: Python<'py>, request: &Bound<'py, PyAny>) -> PyResult = request.getattr("concurrency")?; let concurrency: i64 = if requested.is_none() { - call_with( - ®istry::native(py, NativeAttr::BuildEffectiveSettingsConfig)?, - &[("discovered_inputs", project.discovered.clone())], - )? - .getattr("concurrency")? - .extract()? + build_effective_settings_config(&project.discovered.clone())? + .getattr("concurrency")? + .extract()? } else { requested.extract()? }; @@ -145,29 +144,34 @@ fn finish( flush(&run.stream)?; write_completion(&run.stream, (&run.project.namespace, succeeded))?; let namespace: &ScenarioNamespace = &run.project.namespace; - let payload: Bound<'_, PyAny> = call_with( - ®istry::native(py, NativeAttr::FormatScenarioExecutionJson)?, - &[ - ("results", results.clone().into_any()), - ("local", boolean(py, run.local)), - ( - "run_namespace", - optional_text(py, namespace.value.as_deref()), - ), - ("namespace_source", text(py, &namespace.source)), - ( - "duration_ms", - pyo3::types::PyFloat::new(py, elapsed_seconds * 1000.0).into_any(), - ), - ], - )?; - call_with( - ®istry::native(py, NativeAttr::WriteExecutionJsonOutput)?, - &[ - ("payload", payload), - ("json_output", request.getattr("json_output")?), - ("json_output_path", request.getattr("json_output_path")?), - ], + let payload: Bound<'_, PyAny> = PyString::new( + py, + &format_scenario_execution_json( + py, + Some(keywords( + py, + &[ + ("results", results.clone().into_any()), + ("local", boolean(py, run.local)), + ( + "run_namespace", + optional_text(py, namespace.value.as_deref()), + ), + ("namespace_source", text(py, &namespace.source)), + ( + "duration_ms", + pyo3::types::PyFloat::new(py, elapsed_seconds * 1000.0).into_any(), + ), + ], + )?), + )?, + ) + .into_any(); + write_execution_json_output( + py, + payload, + request.getattr("json_output")?.extract()?, + request.getattr("json_output_path")?, )?; Ok(()) } 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 82362541fa..af12efe4f3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/seed_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/seed_command/command.rs @@ -14,14 +14,26 @@ use sqlbuild_core::seed_command::main::render_seed_row::render_seed_row; use sqlbuild_core::seed_command::main::seed_exit_code::seed_exit_code; use sqlbuild_core::seed_command::models::SeedRow; +use crate::bindings::_helpers::cli::progress::current_native_progress_projector; +use crate::bindings::_helpers::cli::progress_reporters::ConnectionProgressReporter; +use crate::bindings::_helpers::cli::progress_text::write_execution_header; use crate::bindings::_helpers::command_runtime::invocation::resolve_command_invocation; +use crate::bindings::_helpers::compile_driver::pipeline_entries::build_static_pipeline_result; use crate::bindings::_helpers::compile_driver::plan_result::connection_with_hooks; use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::compile_driver::python_calls::keywords; +use crate::bindings::_helpers::compile_driver::selection_phases::compile_static_command_context; +use crate::bindings::_helpers::dbt_interop::reference_resolver::resolve_external_sql_reference_resolver; use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::planner_entries::command_plans::build_seed_command_plan; +use crate::bindings::_helpers::planner_entries::command_plans::read_selected_seed_fingerprints; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; +use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::result_output::documents::format_seed_execution_json; +use crate::bindings::_helpers::result_output::documents::write_execution_json_output; +use crate::bindings::_helpers::result_output::event_writer::ExecutionEventWriter; const SUCCESS_STATUS: &str = "success"; const FAILED_STATUS: &str = "failed"; @@ -176,25 +188,26 @@ fn compile_seeds<'py>( let adapter: Bound<'py, PyAny> = native_field(invocation, "adapter")?; let connection_config: Bound<'py, PyAny> = native_field(invocation, "connection_config")?; let discovered: Bound<'py, PyAny> = native_field(invocation, "discovered_inputs")?; - let connection_progress: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, - &[ - ("adapter_name", native_field(invocation, "adapter_name")?), - ("stream", stream.clone()), - ("use_color", native_field(invocation, "use_color")?), - ], - )?; + let connection_progress: Bound<'py, PyAny> = Bound::new( + py, + ConnectionProgressReporter::new( + py, + Some(keywords( + py, + &[ + ("adapter_name", native_field(invocation, "adapter_name")?), + ("stream", stream.clone()), + ("use_color", native_field(invocation, "use_color")?), + ], + )?), + )?, + )? + .into_any(); stream.call_method1("write", ("\n",))?; stream.call_method0("flush")?; - let resolver: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::ResolveExternalSqlReferenceResolver)?, - &[ - ( - "project_dir", - native_field(invocation, "effective_project_dir")?, - ), - ("discovered_inputs", discovered.clone()), - ], + let resolver: Bound<'py, PyAny> = resolve_external_sql_reference_resolver( + native_field(invocation, "effective_project_dir")?, + discovered.clone(), )?; let options: Bound<'py, PyAny> = call_with( ®istry::class(py, RecordId::CompilePipelineOptions)?, @@ -207,14 +220,14 @@ fn compile_seeds<'py>( ("external_sql_reference_resolver", resolver), ], )?; - let context: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::CompileStaticCommandContext)?, + let context: Bound<'py, PyAny> = compile_static_command_context(Some(&keywords( + py, &[ ("discovered_inputs", discovered), ("adapter", adapter.clone()), ("options", options), ], - )?; + )?))?; let project: Bound<'py, PyAny> = native_field(&context, "project")?; let scope: Bound<'py, PyAny> = native_field(&context, "scope")?; let selected_keys: Bound<'py, PyAny> = native_field(&scope, "selected_keys")?; @@ -248,31 +261,29 @@ fn compile_seeds<'py>( )?; let connection: Bound<'py, PyAny> = connection_with_hooks(&adapter, &connection_config, &hooks)?; - let read: PyResult> = call_with( - ®istry::native(py, NativeAttr::ReadSelectedSeedFingerprints)?, - &[ - ("adapter", adapter.clone()), - ("connection", connection.clone()), - ("seeds", PyTuple::new(py, &selected)?.into_any()), - ], - ); + let read: PyResult> = read_selected_seed_fingerprints( + &adapter, + &connection, + &PyTuple::new(py, &selected)?.into_any(), + ) + .map(Bound::into_any); let closed: PyResult> = adapter.call_method1("close", (connection,)); let read: Bound<'py, PyAny> = read?; closed?; read }; - let plan_output: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::BuildSeedCommandPlan)?, - &[ - ("project", project), - ("scope", scope), - ("relations", native_field(&context, "relations")?), - ("fingerprints", fingerprints), - ], + let plan_output: Bound<'py, PyAny> = build_seed_command_plan( + &project, + &scope, + &native_field(&context, "relations")?, + Some(fingerprints), )?; - call_with( - ®istry::native(py, NativeAttr::BuildStaticPipelineResult)?, - &[("context", context), ("plan_output", plan_output)], + build_static_pipeline_result( + py, + Some(keywords( + py, + &[("context", context), ("plan_output", plan_output)], + )?), ) } @@ -304,7 +315,7 @@ fn write_header( header.set_item("target", py.None())?; header.set_item("concurrency", concurrency)?; header.set_item("use_color", use_color)?; - registry::native(py, NativeAttr::WriteExecutionHeader)?.call((), Some(&header))?; + write_execution_header(py, Some(header))?; Ok(()) } @@ -326,9 +337,8 @@ fn load_seeds<'py>( .map(|(index, name)| (name.clone(), index + 1)) .collect(); let event_writer: Bound<'py, PyAny> = - registry::native(py, NativeAttr::ExecutionEventWriter)?.call0()?; - let projector: Bound<'py, PyAny> = - registry::native(py, NativeAttr::CurrentNativeProgressProjector)?.call0()?; + Bound::new(py, ExecutionEventWriter::new(py, None)?)?.into_any(); + let projector: Bound<'py, PyAny> = current_native_progress_projector(py)?; let projector: Option> = if projector.is_none() { None } else { @@ -357,18 +367,25 @@ fn load_seeds<'py>( projector, }, )?; - let connection_progress: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, - &[ - ("adapter_name", native_field(invocation, "adapter_name")?), - ("stream", stream), - ( - "blank_line_after_complete", - PyBool::new(py, true).to_owned().into_any(), - ), - ("use_color", use_color), - ], - )?; + let connection_progress: Bound<'py, PyAny> = Bound::new( + py, + ConnectionProgressReporter::new( + py, + Some(keywords( + py, + &[ + ("adapter_name", native_field(invocation, "adapter_name")?), + ("stream", stream), + ( + "blank_line_after_complete", + PyBool::new(py, true).to_owned().into_any(), + ), + ("use_color", use_color), + ], + )?), + )?, + )? + .into_any(); let relay: Bound<'py, SeedConnectionRelay> = connection_relay(&connection_progress)?; let loaded: PyResult>> = (|| { call_with( @@ -459,23 +476,20 @@ fn run_seed_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult ),), )?; stream.call_method0("flush")?; - let payload: Bound<'_, PyAny> = call_with( - ®istry::native(py, NativeAttr::FormatSeedExecutionJson)?, - &[ - ("results", PyTuple::new(py, &results)?.into_any()), - ("plan", native_field(&pipeline, "plan_output")?), - ], - )?; - call_with( - ®istry::native(py, NativeAttr::WriteExecutionJsonOutput)?, - &[ - ("payload", payload), - ("json_output", native_field(request, "json_output")?), - ( - "json_output_path", - native_field(request, "json_output_path")?, - ), - ], + let payload: Bound<'_, PyAny> = PyString::new( + py, + &format_seed_execution_json( + py, + PyTuple::new(py, &results)?.into_any(), + native_field(&pipeline, "plan_output")?, + )?, + ) + .into_any(); + write_execution_json_output( + py, + payload, + native_field(request, "json_output")?.extract()?, + native_field(request, "json_output_path")?, )?; Ok(seed_exit_code(failed)) } 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 4019dbc047..1e51b833c9 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 @@ -23,14 +23,28 @@ use sqlbuild_core::test_command::models::{ TestFailure, TestInspectionEntry, TestParameter, TestPlanWarning, }; +use crate::bindings::_helpers::build_progress::nested_progress::NestedCommandProgressCallbacks; use crate::bindings::_helpers::build_progress::transient_status::status_reporter; +use crate::bindings::_helpers::cli::progress::current_native_progress_projector; +use crate::bindings::_helpers::cli::progress_reporters::ConnectionProgressReporter; +use crate::bindings::_helpers::cli::progress_text::{ + format_expectation_detail, format_expectation_name, write_execution_header, +}; use crate::bindings::_helpers::command_runtime::invocation::resolve_reported_command_invocation; +use crate::bindings::_helpers::compile_driver::pipeline_entries::build_static_pipeline_result; +use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::compile_driver::selection_phases::compile_static_command_context; use crate::bindings::_helpers::compile_outputs::runtime_target::write_test_runtime_target; +use crate::bindings::_helpers::dbt_interop::reference_resolver::resolve_external_sql_reference_resolver; 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::planner_entries::command_plans::build_test_command_plan; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::result_output::documents::format_test_execution_json; +use crate::bindings::_helpers::result_output::documents::write_execution_json_output; +use crate::bindings::_helpers::result_output::event_writer::ExecutionEventWriter; use crate::bindings::_helpers::result_output::integration_values::json_value; use crate::bindings::_helpers::result_output::resource_ids::sql_test_identity; @@ -147,8 +161,6 @@ fn outcome_status(outcome: &Bound<'_, PyAny>) -> PyResult<&'static str> { fn expectation_rows<'py>(result: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = result.py(); 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?; @@ -156,12 +168,18 @@ fn expectation_rows<'py>(result: &Bound<'py, PyAny>) -> PyResult()?), + ), + ), ( "status_text", text(py, outcome_status(&step.getattr("outcome")?)?), ), - ("detail", detail.call1((&step,))?), + ("detail", text(py, &format_expectation_detail(&step)?)), ], )?); } @@ -305,12 +323,9 @@ fn compile_tests<'py>( let py: Python<'py> = request.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( - ®istry::native(py, NativeAttr::ResolveExternalSqlReferenceResolver)?, - &[ - ("project_dir", invocation.getattr("effective_project_dir")?), - ("discovered_inputs", discovered.clone()), - ], + let resolver: Bound<'py, PyAny> = resolve_external_sql_reference_resolver( + invocation.getattr("effective_project_dir")?, + discovered.clone(), )?; let options: Bound<'py, PyAny> = call_with( ®istry::class(py, RecordId::CompilePipelineOptions)?, @@ -335,8 +350,8 @@ fn compile_tests<'py>( ("external_sql_reference_resolver", resolver), ], )?; - let context: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::CompileStaticCommandContext)?, + let context: Bound<'py, PyAny> = compile_static_command_context(Some(&keywords( + py, &[ ("discovered_inputs", discovered), ("adapter", adapter.clone()), @@ -348,17 +363,20 @@ fn compile_tests<'py>( .getattr("on_progress")?, ), ], - )?; + )?))?; let case_name: Bound<'py, PyAny> = request.getattr("case_name")?; - let plan_output: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::BuildTestCommandPlan)?, - &[ - ("project", context.getattr("project")?), - ("adapter", adapter), - ("scope", context.getattr("scope")?), - ("relations", context.getattr("relations")?), - ("case_name", case_name.clone()), - ], + let plan_output: Bound<'py, PyAny> = build_test_command_plan( + py, + Some(keywords( + py, + &[ + ("project", context.getattr("project")?), + ("adapter", adapter), + ("scope", context.getattr("scope")?), + ("relations", context.getattr("relations")?), + ("case_name", case_name.clone()), + ], + )?), )?; if !case_name.is_none() && !plan_output.getattr("test_entries")?.is_truthy()? { let cases: Vec = strings(plan_output.getattr("available_test_case_names")?)?; @@ -375,9 +393,12 @@ fn compile_tests<'py>( ), )); } - call_with( - ®istry::native(py, NativeAttr::BuildStaticPipelineResult)?, - &[("context", context), ("plan_output", plan_output)], + build_static_pipeline_result( + py, + Some(keywords( + py, + &[("context", context), ("plan_output", plan_output)], + )?), ) } @@ -463,22 +484,28 @@ 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( - ®istry::native(py, NativeAttr::NestedCommandProgressCallbacks)?, - &[ - ("total", PyInt::new(py, test_count).into_any()), - ("label", text(py, "test")), - ("stream", stream.clone()), - ("use_color", use_color.clone()), - ( - "name_width", - PyInt::new(py, name_width(&entries)?).into_any(), - ), - ("concurrency", PyInt::new(py, worker_count).into_any()), - ], - )?; - let projector: Bound<'py, PyAny> = - registry::native(py, NativeAttr::CurrentNativeProgressProjector)?.call0()?; + let progress: Bound<'py, PyAny> = Bound::new( + py, + NestedCommandProgressCallbacks::new( + py, + Some(keywords( + py, + &[ + ("total", PyInt::new(py, test_count).into_any()), + ("label", text(py, "test")), + ("stream", stream.clone()), + ("use_color", use_color.clone()), + ( + "name_width", + PyInt::new(py, name_width(&entries)?).into_any(), + ), + ("concurrency", PyInt::new(py, worker_count).into_any()), + ], + )?), + )?, + )? + .into_any(); + let projector: Bound<'py, PyAny> = current_native_progress_projector(py)?; if !projector.is_none() { for entry in entries.try_iter()? { call_with( @@ -492,17 +519,24 @@ fn run_tests<'py>( (render_test_ready((test_count, models.len()), style),), )?; stream.call_method0("flush")?; - let connection: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, - &[ - ("adapter_name", invocation.getattr("adapter_name")?), - ("stream", stream.clone()), - ("use_color", use_color.clone()), - ], - )?; + let connection: Bound<'py, PyAny> = Bound::new( + py, + ConnectionProgressReporter::new( + py, + Some(keywords( + py, + &[ + ("adapter_name", invocation.getattr("adapter_name")?), + ("stream", stream.clone()), + ("use_color", use_color.clone()), + ], + )?), + )?, + )? + .into_any(); let preflight: Bound<'py, PyAny> = status_reporter(&stream, use_color.is_truthy()?)?; let event_writer: Bound<'py, PyAny> = - registry::native(py, NativeAttr::ExecutionEventWriter)?.call0()?; + Bound::new(py, ExecutionEventWriter::new(py, None)?)?.into_any(); let relay: Bound<'py, TestCallbackRelay> = Bound::new( py, TestCallbackRelay { @@ -595,17 +629,13 @@ fn complete( )?; stream.call_method0("flush")?; let tuple: Bound<'_, PyAny> = PyTuple::new(py, results)?.into_any(); - let payload: Bound<'_, PyAny> = call_with( - ®istry::native(py, NativeAttr::FormatTestExecutionJson)?, - &[("results", tuple)], - )?; - call_with( - ®istry::native(py, NativeAttr::WriteExecutionJsonOutput)?, - &[ - ("payload", payload), - ("json_output", request.getattr("json_output")?), - ("json_output_path", request.getattr("json_output_path")?), - ], + let payload: Bound<'_, PyAny> = + PyString::new(py, &format_test_execution_json(py, tuple)?).into_any(); + write_execution_json_output( + py, + payload, + request.getattr("json_output")?.extract()?, + request.getattr("json_output_path")?, )?; Ok(test_exit_code(passed, results.len())) } @@ -642,7 +672,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")?)?; - registry::native(py, NativeAttr::WriteExecutionHeader)?.call((), Some(&header))?; + write_execution_header(py, Some(header))?; let results: Vec> = run_tests(&invocation, &pipeline, concurrency)?; write_test_runtime_target( ( diff --git a/tests/unit/src/sqlbuild/cli/commands/main/execution/helpers.py b/tests/unit/src/sqlbuild/cli/commands/main/execution/helpers.py index 6b1431bc9e..b969aa9ef6 100644 --- a/tests/unit/src/sqlbuild/cli/commands/main/execution/helpers.py +++ b/tests/unit/src/sqlbuild/cli/commands/main/execution/helpers.py @@ -4,19 +4,10 @@ from contextlib import nullcontext from io import StringIO from pathlib import Path -from typing import Any from unittest.mock import Mock import pytest -from sqlbuild import _native as native -from sqlbuild._native import ( - CompiledObjectKey, - CompiledResourceType, - CompiledSqlScenario, - DiscoveredSqlScenarioFile, -) -from sqlbuild.adapter.discovery.main import resolve_adapter as adapters_module from sqlbuild.diagnostics.main import process_resource_reporting as resource_reporting_module from sqlbuild.executor.pipeline.main import run as executor_run_module from tests.unit.src.sqlbuild.presentation.helpers import use_plain_terminal @@ -49,45 +40,49 @@ def isatty(self) -> bool: return self._tty +_SCENARIO_PROJECT_TOML: str = ( + 'name = "project"\nadapter = "duckdb"\n\n[connection]\ndatabase = "project.duckdb"\n' +) +_SCENARIO_SOURCES_YAML: str = ( + "sources:\n" + " - name: raw_orders\n description: Test source raw_orders.\n" + " schema: main\n" + " table: raw_orders\n" + " columns:\n" + " - name: order_id\n type: INTEGER\n nullable: false\n" +) +_SCENARIO_MODEL_SQL: str = ( + 'MODEL (description "Test model orders.");\n\nSELECT order_id FROM __source("raw_orders")\n' +) +VALID_SCENARIO_SQL: str = ( + "SCENARIO (description 'Test scenario orders_paid.');\n\n" + "WITH\n" + "__source__raw_orders AS (SELECT 1 AS order_id),\n" + "__expected__orders AS (SELECT 1 AS order_id)\n" + "SELECT 1\n" +) +INVALID_SCENARIO_SQL: str = "SCENARIO(description 'Test scenario.');\n\nSELECT 1\n" + + def configure_native_scenario_command( *, monkeypatch: pytest.MonkeyPatch, stream: ScenarioProgressStream, - compile_project: Any, + scenario_sql: str, project_dir: Path, ) -> None: - """Isolate the native scenario command from adapters and the warehouse in an empty project.""" - - _ = (project_dir / "sqlbuild_project.toml").write_text( - 'name = "project"\nadapter = "duckdb"\n', encoding="utf-8" - ) + """Write a one-scenario DuckDB project and isolate the command from scenario execution.""" + + files: dict[str, str] = { + "sqlbuild_project.toml": _SCENARIO_PROJECT_TOML, + "sources/raw.yml": _SCENARIO_SOURCES_YAML, + "models/orders.sql": _SCENARIO_MODEL_SQL, + "tests/scenarios/orders_paid.sql": scenario_sql, + } + for relative_path, contents in files.items(): + path: Path = project_dir / relative_path + path.parent.mkdir(parents=True, exist_ok=True) + _ = path.write_text(contents, encoding="utf-8") monkeypatch.setattr(sys, "stdout", stream) use_plain_terminal(monkeypatch) - monkeypatch.setattr(native, "resolve_project_connection_config", lambda **_: {}) - monkeypatch.setattr(native, "run_compile_only_pipeline", compile_project) - monkeypatch.setattr(adapters_module, "resolve_adapter", lambda **_: Mock()) - monkeypatch.setattr(native, "resolve_external_sql_reference_resolver", lambda **_: None) monkeypatch.setattr(executor_run_module, "run_scenario_test_pipeline", lambda **_: ()) - - -def build_compiled_scenario_project(project_dir: Path) -> Mock: - """A compile result with one scenario under tests/scenarios.""" - - scenario_file: DiscoveredSqlScenarioFile = DiscoveredSqlScenarioFile( - file_path=project_dir / "tests/scenarios/orders_paid.sql", - relative_path=Path("tests/scenarios/orders_paid.sql"), - contents="SCENARIO(description 'Test scenario.');\n\nSELECT 1\n", - header_values={}, - sql_body="SELECT 1", - name="orders_paid", - ) - pipeline_result: Mock = Mock() - pipeline_result.project.sql_scenarios = ( - CompiledSqlScenario( - key=CompiledObjectKey(CompiledResourceType.SQL_SCENARIO, "orders_paid"), - name="orders_paid", - scenario_file=scenario_file, - sql_body="SELECT 1", - ), - ) - return pipeline_result diff --git a/tests/unit/src/sqlbuild/cli/commands/main/execution/test_scenario_command.py b/tests/unit/src/sqlbuild/cli/commands/main/execution/test_scenario_command.py index 4adb978cb5..4db2347d6e 100644 --- a/tests/unit/src/sqlbuild/cli/commands/main/execution/test_scenario_command.py +++ b/tests/unit/src/sqlbuild/cli/commands/main/execution/test_scenario_command.py @@ -2,13 +2,11 @@ from contextvars import Token from pathlib import Path -from typing import Any -from unittest.mock import Mock import pytest from sqlbuild._native import ( - CompilePipelineResult, + CompileInputError, NativeProgressProjector, ScenarioTestCommandRequest, run_scenario_test_command, @@ -18,8 +16,9 @@ ScenarioCompilePresentationTestCase, ) from tests.unit.src.sqlbuild.cli.commands.main.execution.helpers import ( + INVALID_SCENARIO_SQL, + VALID_SCENARIO_SQL, ScenarioProgressStream, - build_compiled_scenario_project, configure_native_scenario_command, ) @@ -50,18 +49,10 @@ def test_given_scenario_compile_when_running_then_presentation_has_one_owner( test_case: ScenarioCompilePresentationTestCase, ) -> None: stream: ScenarioProgressStream = ScenarioProgressStream(tty=test_case.tty) - pipeline_result: Mock = build_compiled_scenario_project(tmp_path) - - def compile_project(**kwargs: Any) -> CompilePipelineResult: - on_progress: Any = kwargs["on_progress"] - on_progress("Compiling project...") - on_progress("Compiled project.") - return pipeline_result - configure_native_scenario_command( monkeypatch=monkeypatch, stream=stream, - compile_project=compile_project, + scenario_sql=VALID_SCENARIO_SQL, project_dir=tmp_path, ) events: list[LifecycleEvent] = [] @@ -115,16 +106,10 @@ def test_given_scenario_compile_failure_when_running_then_lifecycle_fails_and_pr test_case: ScenarioCompilePresentationTestCase, ) -> None: stream: ScenarioProgressStream = ScenarioProgressStream(tty=test_case.tty) - - def fail_compile(**kwargs: Any) -> CompilePipelineResult: - on_progress: Any = kwargs["on_progress"] - on_progress("Compiling project...") - raise RuntimeError("compile failed") - configure_native_scenario_command( monkeypatch=monkeypatch, stream=stream, - compile_project=fail_compile, + scenario_sql=INVALID_SCENARIO_SQL, project_dir=tmp_path, ) events: list[LifecycleEvent] = [] @@ -136,7 +121,7 @@ def fail_compile(**kwargs: Any) -> CompilePipelineResult: try: with dispatcher_scope(dispatcher): - with pytest.raises(RuntimeError, match="compile failed"): + with pytest.raises(CompileInputError, match="must declare fixture CTEs"): run_scenario_test_command(request=ScenarioTestCommandRequest(project_dir=tmp_path)) finally: projector.restore(token) diff --git a/tests/unit/src/sqlbuild/cli/commands/main/scope/test_scope.py b/tests/unit/src/sqlbuild/cli/commands/main/scope/test_scope.py index 4f82827a5a..17608c3ab7 100644 --- a/tests/unit/src/sqlbuild/cli/commands/main/scope/test_scope.py +++ b/tests/unit/src/sqlbuild/cli/commands/main/scope/test_scope.py @@ -7,7 +7,6 @@ import pytest -from sqlbuild import _native as native from sqlbuild._native import ScopeCommandRequest, ScopeCompleteness, ScopeIndex, run_scope_command from sqlbuild.adapter.discovery.main import resolve_adapter as adapter_registry from tests.unit.src.sqlbuild.cli.commands.main.scope._test_types import ScopeCommandCase @@ -25,7 +24,6 @@ def fail(**_kwargs: object) -> None: pytest.fail("scope must not resolve adapters or warehouse credentials") monkeypatch.setattr(adapter_registry, "resolve_adapter", fail) - monkeypatch.setattr(native, "resolve_project_connection_config", fail) exit_code: int = run_scope_command( request=ScopeCommandRequest(target="model:orders", json_output=True), load_scope_index=lambda **_kwargs: report_scope_lookup().index, From d91b63239f2195022e3584c9fce90e4e5908090b Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Sat, 17 Oct 2026 01:37:02 +0100 Subject: [PATCH 02/28] refactor: build native records through classes captured at module init Add records::construct (record_class, new_record, new_record_with, enum_member, enum_from_value) holding every record class and string enum captured once at init, and use it for analysis bindings instead of a module attribute lookup per construction. --- .../_helpers/analysis/binding_catalog.rs | 10 ++- .../_helpers/analysis/explicit_references.rs | 25 ++++--- .../_helpers/analysis/reference_facts.rs | 6 +- .../_helpers/analysis/required_columns.rs | 9 ++- .../_helpers/analysis/schema_request.rs | 9 ++- .../analysis_session/fixture_columns.rs | 21 +++--- .../analysis_session/model_analyses.rs | 11 ++- .../analysis_session/outcome_objects.rs | 27 +++---- .../analysis_session/profile_inputs.rs | 4 +- .../analysis_session/stage_functions.rs | 24 +++++-- .../bindings/_helpers/records/construct.rs | 70 +++++++++++++++++++ .../src/bindings/_helpers/records/mod.rs | 1 + .../src/bindings/main/register.rs | 1 + 13 files changed, 157 insertions(+), 61 deletions(-) create mode 100644 crates/sqlbuild-python/src/bindings/_helpers/records/construct.rs 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 4402d6e877..5345f27c3c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/analysis/binding_catalog.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/analysis/binding_catalog.rs @@ -11,9 +11,9 @@ 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::construct::new_record; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use crate::bindings::models::{CatalogInput, ProjectCatalog}; use crate::bindings::types::CompilerDetach; @@ -323,9 +323,7 @@ pub(crate) fn schema_validations<'py>( None => groups.push((catalog, own, vec![index])), } } - 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 empty: Bound<'py, PyAny> = new_record(py, RecordId::SqlBindingResult, (), None)?; let mut results: Vec> = vec![empty; requests.len()]; for (catalog, any_explicit, members) in groups { let catalog = catalog.borrow(py); @@ -351,11 +349,11 @@ pub(crate) fn schema_validations<'py>( for (index, diagnostics) in members.into_iter().zip(rows) { let built: Vec> = diagnostics .into_iter() - .map(|row| diagnostic_type.call1(row)) + .map(|row| new_record(py, RecordId::SqlBindingDiagnostic, row, None)) .collect::>()?; let keywords: Bound<'py, PyDict> = PyDict::new(py); keywords.set_item("diagnostics", PyTuple::new(py, built)?)?; - results[index] = result_type.call((), Some(&keywords))?; + results[index] = new_record(py, RecordId::SqlBindingResult, (), Some(&keywords))?; } } PyTuple::new(py, results) 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 9b40f131af..e377332d18 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/analysis/explicit_references.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/analysis/explicit_references.rs @@ -28,6 +28,7 @@ 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::construct::{enum_from_value, new_record, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -309,11 +310,8 @@ fn python_report<'py>( report: &ReferenceReport, owners: &[PythonSqlOwner<'py>], ) -> PyResult> { - 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> = registry::class(py, RecordId::SourceLocation)?; let path_class: Bound<'py, PyAny> = py.import("pathlib")?.getattr("Path")?; - 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); @@ -333,15 +331,23 @@ fn python_report<'py>( location.set_item("path", &owners[owner].relative_path)?; location.set_item("line", row.line)?; location.set_item("column", 1)?; - fields.set_item("location", source_location.call((), Some(&location))?)?; + fields.set_item( + "location", + new_record(py, RecordId::SourceLocation, (), Some(&location))?, + )?; } None => fields.set_item("location", py.None())?, } fields.set_item("help", &row.help)?; - diagnostics.push(compiler_diagnostic.call((), Some(&fields))?); + diagnostics.push(new_record( + py, + RecordId::CompilerDiagnostic, + (), + Some(&fields), + )?); } let literal_relation: Bound<'py, PyAny> = - registry::class(reference_models.py(), RecordId::LiteralSqlRelation)?; + record_class(reference_models.py(), RecordId::LiteralSqlRelation)?; let relation_name: Bound<'py, PyAny> = native_field(&reference_models, "RelationName")?; let mut unmatched: Vec> = Vec::with_capacity(report.unmatched.len()); for row in &report.unmatched { @@ -352,7 +358,10 @@ fn python_report<'py>( relation.set_item("database", &row.relation.database)?; let fields: Bound<'py, PyDict> = PyDict::new(py); fields.set_item("owner_label", &owner.label)?; - fields.set_item("owner_kind", owner_kind.call1((owner.kind,))?)?; + fields.set_item( + "owner_kind", + enum_from_value(py, RecordId::HardCodedRelationOwnerKind, owner.kind)?, + )?; fields.set_item("relative_path", &owner.relative_path)?; fields.set_item("line", row.line)?; fields.set_item("method", &row.method)?; @@ -362,7 +371,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)?)?; - registry::class(py, RecordId::PythonSqlReferenceReport)?.call((), Some(&fields)) + new_record(py, RecordId::PythonSqlReferenceReport, (), 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 be5d4b5aed..dbdc3b4589 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/analysis/reference_facts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/analysis/reference_facts.rs @@ -1,8 +1,8 @@ //! The explicit-reference facts of a compiled project, read from its Python views. +use crate::bindings::_helpers::records::construct::record_class; use crate::bindings::_helpers::records::fields::field as native_field; 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::{ @@ -108,8 +108,8 @@ struct HookEntries<'py> { impl<'py> HookEntries<'py> { fn imported(py: Python<'py>) -> PyResult { Ok(Self { - python: registry::class(py, RecordId::PythonHookEntry)?, - sql: registry::class(py, RecordId::SqlHookEntry)?, + python: record_class(py, RecordId::PythonHookEntry)?, + sql: record_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 ca168965cf..4afeef9f3c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/analysis/required_columns.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/analysis/required_columns.rs @@ -9,10 +9,9 @@ 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; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use crate::bindings::types::CompilerDetach; /// CTE-aware column reads of `query_sql`, resolved to the external resources it references. @@ -41,11 +40,11 @@ fn analyze_resolved_column_reads<'py>( } RequiredColumnsError::Normalization(message) => value_error(message), })?; - 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( - &fact, + facts.push(new_record_with( + py, + RecordId::CompiledLineageSourceFact, &[ ( "resource_type", 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 13d49e2f2b..b2fc9f5f25 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/analysis/schema_request.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/analysis/schema_request.rs @@ -8,8 +8,8 @@ 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::construct::new_record; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use crate::bindings::types::CompilerDetach; const GENERIC_DIALECT: &str = "generic"; @@ -61,5 +61,10 @@ pub(crate) fn schema_validation_request<'py>( parts.quoted_identifiers_ignore_case, )?; arguments.set_item("catalog", parts.catalog)?; - registry::class(py, RecordId::SqlSchemaValidationRequest)?.call((), Some(&arguments)) + new_record( + py, + RecordId::SqlSchemaValidationRequest, + (), + 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 b8d14bb4b6..133c509880 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,9 +14,9 @@ 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::construct::{enum_from_value, new_record}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use crate::bindings::types::CompilerDetach; const PROJECTION_FACTS_CONTEXT: &str = "native fixture projection facts"; @@ -36,8 +36,7 @@ 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> = - registry::class(py, RecordId::NativeColumnQuery)?.call((), Some(&query))?; + let query: Bound<'py, PyAny> = new_record(py, RecordId::NativeColumnQuery, (), Some(&query))?; let empty: Bound<'py, PyAny> = PyDict::new(py).into_any(); let rows: Vec = query_column_rows( PyTuple::new(py, [query])?.as_any(), @@ -52,8 +51,6 @@ 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> = registry::class(py, RecordId::InferredColumn)?; - let nullability_class: Bound<'py, PyAny> = registry::class(py, RecordId::InferredNullability)?; let mut built: Vec> = Vec::with_capacity(columns.len()); for (name, data_type, nullability) in columns { let data_type: Option = data_type @@ -62,8 +59,16 @@ fn infer_fixture_column_facts<'py>( let arguments: Bound<'py, PyDict> = PyDict::new(py); arguments.set_item("name", name)?; arguments.set_item("type", data_type)?; - arguments.set_item("nullability", nullability_class.call1((nullability,))?)?; - built.push(column_class.call((), Some(&arguments))?); + arguments.set_item( + "nullability", + enum_from_value(py, RecordId::InferredNullability, nullability)?, + )?; + built.push(new_record( + py, + RecordId::InferredColumn, + (), + Some(&arguments), + )?); } let arguments: Bound<'py, PyDict> = PyDict::new(py); arguments.set_item("columns", PyTuple::new(py, built)?)?; @@ -72,7 +77,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)?)?; - registry::class(py, RecordId::FixtureColumnInference)?.call((), Some(&arguments)) + new_record(py, RecordId::FixtureColumnInference, (), Some(&arguments)) } pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { 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 d305219977..ce409db471 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 @@ -22,9 +22,9 @@ use crate::bindings::_helpers::compiled_project::model_input_view::{ }; use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::construct::enum_member; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::semantic_shapes::shapes::{VALID_FROM_DEFAULT, VALID_TO_DEFAULT}; const LOGGING_MODULE: &str = "logging"; @@ -152,8 +152,7 @@ fn run_session<'py>( PyString::new(py, &failure).as_any(), )?; } - let unknown: Bound<'py, PyAny> = - registry::class(py, RecordId::InferredNullability)?.getattr("UNKNOWN")?; + let unknown: Bound<'py, PyAny> = enum_member(py, RecordId::InferredNullability, "UNKNOWN")?; for (name, shape) in publications { let published: Bound<'py, PyDict> = shape.into_py_dict(py)?; types.call_method1("setdefault", (&name, &published))?; @@ -256,10 +255,8 @@ fn model_rows<'py>( py: Python<'py>, model_inputs: &[Bound<'py, PyAny>], ) -> PyResult> { - 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 enforced: Bound<'py, PyAny> = enum_member(py, RecordId::ContractPolicy, "ENFORCED")?; + let snapshot: Bound<'py, PyAny> = enum_member(py, RecordId::MaterializationType, "SNAPSHOT")?; let mut rows: Vec> = Vec::with_capacity(model_inputs.len()); for model_input in model_inputs { let values: Bound<'py, PyAny> = native_field(model_input, "config")?.getattr("values")?; 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 a93bbd7512..c873af4531 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 @@ -9,8 +9,8 @@ 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::construct::{enum_from_value, new_record, record_class}; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; const LINEAGE_NATIVE: &str = "native"; @@ -57,15 +57,14 @@ fn session_outcome_analysis<'py>( } else { PyTuple::empty(py).into_any() }; - 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> = PyTuple::new(py, row?.try_iter()?.collect::>>()?)?; - bindings.push(binding_class.call1(row)?); + bindings.push(new_record(py, RecordId::SqlBindingDiagnostic, row, None)?); } let result: Bound<'py, PyAny> = call( - ®istry::class(py, RecordId::PolyglotAnalysisResult)?, + &record_class(py, RecordId::PolyglotAnalysisResult)?, &[ ("analysis_succeeded", succeeded), ("columns", inferred_columns(&columns)?), @@ -80,7 +79,7 @@ fn session_outcome_analysis<'py>( ], )?; call( - ®istry::class(py, RecordId::ModelSqlAnalysis)?, + &record_class(py, RecordId::ModelSqlAnalysis)?, &[ ("polyglot_analysis", result), ("placeholders", request.get("placeholders")?), @@ -104,7 +103,7 @@ fn proof<'py>(row: &Bound<'py, PyAny>) -> PyResult> { } let (output_proven, columns, families, inputs, failure_reason, bare): ProofObjects<'py> = row.extract()?; - let family_class: Bound<'py, PyAny> = registry::class(py, RecordId::DynamicColumnFamilyProof)?; + let family_class: Bound<'py, PyAny> = record_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()?; @@ -120,7 +119,7 @@ fn proof<'py>(row: &Bound<'py, PyAny>) -> PyResult> { PyTuple::empty(py).into_any() }; call( - ®istry::class(py, RecordId::DynamicColumnContractProof)?, + &record_class(py, RecordId::DynamicColumnContractProof)?, &[ ("output_proven", output_proven), ("fixed_columns", fixed), @@ -138,8 +137,7 @@ fn inferred_columns<'py>(rows: &Bound<'py, PyAny>) -> PyResult if rows.is_none() { return Ok(rows.clone()); } - let column_class: Bound<'py, PyAny> = registry::class(py, RecordId::InferredColumn)?; - let nullability_class: Bound<'py, PyAny> = registry::class(py, RecordId::InferredNullability)?; + let column_class: Bound<'py, PyAny> = record_class(py, RecordId::InferredColumn)?; let mut columns: Vec> = Vec::new(); for row in rows.try_iter()? { let (name, data_type, nullability): ( @@ -152,7 +150,10 @@ fn inferred_columns<'py>(rows: &Bound<'py, PyAny>) -> PyResult &[ ("name", name), ("type", data_type), - ("nullability", nullability_class.call1((nullability,))?), + ( + "nullability", + enum_from_value(py, RecordId::InferredNullability, nullability)?, + ), ], )?); } @@ -209,7 +210,7 @@ fn compact_lineage<'py>(rows: &Bound<'py, PyAny>) -> PyResult> } let keys: Bound<'py, PyAny> = PyTuple::new(py, pool.keys())?.into_any(); call( - ®istry::class(py, RecordId::CompactLineageFacts)?, + &record_class(py, RecordId::CompactLineageFacts)?, &[ ("string_pool", keys), ("rows", PyTuple::new(py, compact)?.into_any()), @@ -220,8 +221,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 column_class: Bound<'py, PyAny> = registry::class(py, RecordId::CompiledLineageColumnFact)?; - let source_class: Bound<'py, PyAny> = registry::class(py, RecordId::CompiledLineageSourceFact)?; + let column_class: Bound<'py, PyAny> = record_class(py, RecordId::CompiledLineageColumnFact)?; + let source_class: Bound<'py, PyAny> = record_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 f93ebe17f3..1cf052871d 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 @@ -9,9 +9,9 @@ use crate::bindings::_helpers::analysis::binding_catalog::{ }; use crate::bindings::_helpers::native_host::adapter::AdapterHost; use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::construct::record_class; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; const GENERIC_DIALECT: &str = "generic"; const ADAPTER_RULE_ID: &str = "python"; @@ -55,7 +55,7 @@ pub(crate) fn nullability_rules(profile: &Bound<'_, PyAny>) -> PyResult<(Pairs, if adapter.is_empty() { return Ok((rows, None)); } - let nullability: Bound<'_, PyAny> = registry::class(py, RecordId::InferredNullability)?; + let nullability: Bound<'_, PyAny> = record_class(py, RecordId::InferredNullability)?; Ok((rows, Some((adapter.unbind(), nullability.unbind())))) } 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 0239b771e3..0ca962990d 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 @@ -23,9 +23,9 @@ use crate::bindings::_helpers::boundary::keyword_request::Request; use crate::bindings::_helpers::compile_driver::stage_work::{ EXPRESSION_SHAPE_BATCHES, EXPRESSION_SHAPES_INFERRED, count_stage, }; +use crate::bindings::_helpers::records::construct::{enum_from_value, new_record}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use crate::bindings::models::ProjectCatalog; const BATCH_MODE: &str = "batch"; @@ -73,9 +73,6 @@ fn infer_native_query_columns<'py>( &request.get("profile")?, (&types, &nullability), )?; - 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> = registry::class(py, RecordId::InferredNullability)?; let mut analyses: Vec> = Vec::with_capacity(rows.len()); for (succeeded, columns, has_star) in rows { let columns: Bound<'py, PyAny> = match columns { @@ -86,8 +83,16 @@ fn infer_native_query_columns<'py>( let arguments: Bound<'py, PyDict> = PyDict::new(py); arguments.set_item("name", name)?; arguments.set_item("type", data_type)?; - arguments.set_item("nullability", nullability_class.call1((nullability,))?)?; - built.push(column_class.call((), Some(&arguments))?); + arguments.set_item( + "nullability", + enum_from_value(py, RecordId::InferredNullability, nullability)?, + )?; + built.push(new_record( + py, + RecordId::InferredColumn, + (), + Some(&arguments), + )?); } PyTuple::new(py, built)?.into_any() } @@ -96,7 +101,12 @@ fn infer_native_query_columns<'py>( arguments.set_item("succeeded", succeeded)?; arguments.set_item("columns", columns)?; arguments.set_item("has_star", has_star)?; - analyses.push(query_columns.call((), Some(&arguments))?); + analyses.push(new_record( + py, + RecordId::NativeQueryColumns, + (), + Some(&arguments), + )?); } PyTuple::new(py, analyses) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/records/construct.rs b/crates/sqlbuild-python/src/bindings/_helpers/records/construct.rs new file mode 100644 index 0000000000..1375da8705 --- /dev/null +++ b/crates/sqlbuild-python/src/bindings/_helpers/records/construct.rs @@ -0,0 +1,70 @@ +//! Native record classes and string enums held natively, so records are built without a module lookup. + +use pyo3::IntoPyObject; +use pyo3::call::PyCallArgs; +use pyo3::exceptions::PyRuntimeError; +use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyModule, PyResult, Python}; +use pyo3::sync::PyOnceLock; +use pyo3::types::PyDict; + +use crate::bindings::_helpers::compile_driver::python_calls::keywords; +use crate::bindings::_helpers::records::ids::RecordId; + +/// Each `RecordId`'s class, captured when the extension module is initialised. +static CLASSES: PyOnceLock>> = PyOnceLock::new(); + +/// Hold every registered record class and string enum, read once while the module initialises. +pub(crate) fn capture(module: &Bound<'_, PyModule>) -> PyResult<()> { + CLASSES.get_or_try_init(module.py(), || { + RecordId::ALL + .iter() + .map(|id| module.getattr(id.name()).map(Bound::unbind)) + .collect::>>>() + })?; + Ok(()) +} + +/// The native record class or string enum `id` as registered, ignoring later attribute replacement. +pub(crate) fn record_class(py: Python<'_>, id: RecordId) -> PyResult> { + let classes: &Vec> = CLASSES + .get(py) + .ok_or_else(|| PyRuntimeError::new_err("native record classes are not registered"))?; + Ok(classes[id as usize].bind(py).clone()) +} + +/// A new `id` record built exactly as calling its class builds it, finishers included. +pub(crate) fn new_record<'py, A: PyCallArgs<'py>>( + py: Python<'py>, + id: RecordId, + args: A, + kwargs: Option<&Bound<'py, PyDict>>, +) -> PyResult> { + record_class(py, id)?.call(args, kwargs) +} + +/// A new `id` record built from `(field, value)` keyword pairs. +pub(crate) fn new_record_with<'py>( + py: Python<'py>, + id: RecordId, + pairs: &[(&str, Bound<'py, PyAny>)], +) -> PyResult> { + new_record(py, id, (), Some(&keywords(py, pairs)?)) +} + +/// The member `name` of the native string enum `id`, looked up rather than constructed. +pub(crate) fn enum_member<'py>( + py: Python<'py>, + id: RecordId, + name: &str, +) -> PyResult> { + record_class(py, id)?.getattr(name) +} + +/// The member of the native string enum `id` whose value is `value`, as `Enum(value)` finds it. +pub(crate) fn enum_from_value<'py, V: IntoPyObject<'py>>( + py: Python<'py>, + id: RecordId, + value: V, +) -> PyResult> { + record_class(py, id)?.call1((value,)) +} diff --git a/crates/sqlbuild-python/src/bindings/_helpers/records/mod.rs b/crates/sqlbuild-python/src/bindings/_helpers/records/mod.rs index 7d5d3b8228..ea4f36f1a0 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/records/mod.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/records/mod.rs @@ -1,4 +1,5 @@ pub(crate) mod constants; +pub(crate) mod construct; pub(crate) mod error_classes; pub(crate) mod fields; pub(crate) mod ids; diff --git a/crates/sqlbuild-python/src/bindings/main/register.rs b/crates/sqlbuild-python/src/bindings/main/register.rs index 1643c96c6d..29aaf50705 100644 --- a/crates/sqlbuild-python/src/bindings/main/register.rs +++ b/crates/sqlbuild-python/src/bindings/main/register.rs @@ -18,6 +18,7 @@ use crate::bindings::models; pub fn _native(module: &Bound<'_, PyModule>) -> PyResult<()> { records::registration::register(module)?; records::registry::verify_tables()?; + records::construct::capture(module)?; native_host::registration::register(module)?; rules::registration::register(module)?; cli::registration::register(module)?; From 0e32e476c17b53b0c509d8dae037143eab655b20 Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Sat, 17 Oct 2026 02:18:24 +0100 Subject: [PATCH 03/28] refactor: construct native records directly in planner and plan output bindings --- .../bindings/_helpers/plan_command/plan.rs | 6 +-- .../bindings/_helpers/plan_command/rules.rs | 4 +- .../_helpers/plan_output/cursor_details.rs | 12 +++-- .../bindings/_helpers/plan_output/display.rs | 17 ++++--- .../_helpers/plan_output/plan_text.rs | 4 +- .../_helpers/planner_changes/actions.rs | 18 ++++---- .../_helpers/planner_changes/detect.rs | 21 +++++---- .../_helpers/planner_changes/renames.rs | 16 +++---- .../_helpers/planner_changes/results.rs | 17 +++---- .../_helpers/planner_entries/clone_plans.rs | 6 ++- .../_helpers/planner_entries/contexts.rs | 6 +-- .../planner_entries/execution_plan.rs | 4 +- .../_helpers/planner_entries/model_entries.rs | 8 ++-- .../_helpers/planner_entries/plan_output.rs | 4 +- .../_helpers/planner_entries/test_planning.rs | 11 +++-- .../_helpers/planner_entries/values.rs | 5 ++- .../_helpers/planner_identity/graph.rs | 9 ++-- .../_helpers/planner_identity/identities.rs | 11 +++-- .../_helpers/planner_identity/staleness.rs | 17 ++++--- .../planner_migrations/column_entries.rs | 10 ++--- .../planner_migrations/column_objects.rs | 3 +- .../planner_migrations/column_warnings.rs | 12 ++--- .../_helpers/planner_migrations/columns.rs | 9 ++-- .../planner_migrations/compatibility.rs | 18 ++++---- .../planner_migrations/discovery_requests.rs | 7 ++- .../planner_migrations/model_entries.rs | 7 +-- .../planner_migrations/model_planning.rs | 6 +-- .../planner_migrations/old_name_entries.rs | 8 ++-- .../planner_migrations/old_name_objects.rs | 9 ++-- .../planner_migrations/old_name_references.rs | 4 +- .../_helpers/planner_migrations/old_names.rs | 11 ++--- .../planner_migrations/planning_execution.rs | 4 +- .../planner_migrations/planning_origin.rs | 6 +-- .../planner_migrations/projections.rs | 14 +++--- .../_helpers/planner_phases/objects.rs | 12 ++--- .../planner_phases/output_assembly.rs | 5 +-- .../_helpers/planner_phases/retention.rs | 4 +- .../planner_resolution/cursor_bounds.rs | 19 ++++---- .../planner_resolution/cursor_model.rs | 11 +++-- .../planner_resolution/cursor_overrides.rs | 3 +- .../planner_resolution/cursor_policies.rs | 6 +-- .../planner_resolution/cursor_ranges.rs | 16 ++++--- .../planner_resolution/cursor_safety.rs | 14 +++--- .../planner_resolution/cursor_warnings.rs | 10 ++--- .../planner_resolution/cursor_windows.rs | 26 +++++++---- .../planner_resolution/future_safety.rs | 9 ++-- .../planner_resolution/maximum_safety.rs | 8 ++-- .../planner_resolution/orchestration.rs | 3 +- .../_helpers/planner_scenario/artifacts.rs | 45 +++++++++++-------- .../bindings/_helpers/planner_scenario/cli.rs | 10 +++-- .../_helpers/planner_scenario/entries.rs | 11 ++--- .../_helpers/planner_scenario/execution.rs | 27 +++++------ .../_helpers/planner_scenario/fixtures.rs | 11 ++--- .../_helpers/planner_scenario/graph.rs | 15 +++---- .../_helpers/planner_scenario/relations.rs | 12 ++--- .../_helpers/planner_scope/loaders.rs | 4 +- .../bindings/_helpers/planner_scope/scope.rs | 11 ++--- .../_helpers/planner_scope/sources.rs | 10 +++-- .../planner_selection/model_selectors.rs | 9 ++-- .../_helpers/planner_selection/selectors.rs | 19 ++++---- .../_helpers/planner_selection/unit_tests.rs | 13 +++--- .../planner_strategy/audit_entries.rs | 14 +++--- .../_helpers/planner_strategy/audits.rs | 11 ++--- .../planner_strategy/cursor_inputs.rs | 11 +++-- .../planner_strategy/fixture_values.rs | 4 +- .../planner_strategy/microbatch_limits.rs | 13 +++--- .../_helpers/planner_strategy/microbatches.rs | 16 ++++--- .../planner_strategy/model_entries.rs | 12 ++--- .../_helpers/planner_strategy/strategy.rs | 10 ++--- .../_helpers/planner_strategy/values.rs | 7 +-- .../_helpers/planner_warehouse/cursors.rs | 4 +- .../planner_warehouse/source_deferral.rs | 4 +- .../planner_warehouse/source_freshness.rs | 4 +- 73 files changed, 411 insertions(+), 366 deletions(-) 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 b8f276b628..beb7074893 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/plan_command/plan.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/plan_command/plan.rs @@ -24,9 +24,9 @@ use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::plan_output::plan_json::format_plan_json; use crate::bindings::_helpers::plan_output::plan_text::format_plan; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::construct::new_record; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; const COMMAND_NAME: &str = "plan"; const DEFAULT_SECTION_ENTRIES: i64 = 50; @@ -222,7 +222,7 @@ fn compile_pipeline<'py>( )?; options.set_item("record_migration_fingerprints", false)?; let pipeline_options: Bound<'py, PyAny> = - registry::class(py, RecordId::CompilePipelineOptions)?.call((), Some(&options))?; + new_record(py, RecordId::CompilePipelineOptions, (), Some(&options))?; run_compile_pipeline( py, Some(keywords( @@ -275,7 +275,7 @@ fn write_output( ("use_color", flag(py, invocation.use_color)), ( "display_options", - registry::class(py, RecordId::DisplayOptions)?.call((), Some(&display))?, + new_record(py, RecordId::DisplayOptions, (), 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 f51397791d..0b24ec7cba 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/plan_command/rules.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/plan_command/rules.rs @@ -14,9 +14,9 @@ use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::contracts::spec_resolution::resolve_effective_adapter_name; use crate::bindings::_helpers::planner_selection::selectors::select_project_graph; use crate::bindings::_helpers::planner_selection::unit_tests::reject_unit_test_selectors; +use crate::bindings::_helpers::records::construct::new_record; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::catalogue::build_rule_catalogue; use crate::bindings::_helpers::rules::guidance::{format_rule, install_rules_skills}; use crate::bindings::_helpers::rules::results::{ @@ -258,7 +258,7 @@ fn run_rule_selection( fields.set_item(field, result.getattr(field)?)?; } let text: String = format_rules_result( - ®istry::class(py, RecordId::RulesResult)?.call((), Some(&fields))?, + &new_record(py, RecordId::RulesResult, (), Some(&fields))?, native_field(request, "json_output")?.is_truthy()?, )?; print_line(py, &text, false)?; 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 38561eada9..707e8109b6 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 @@ -9,9 +9,9 @@ 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::construct::record_class; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; const MICROBATCH_MODE: &str = "microbatch"; const TIMESTAMP_CURSOR: &str = "timestamp"; @@ -86,15 +86,13 @@ fn count_batches( let (start, end) = (native_field(bounds, "start")?, native_field(bounds, "end")?); if cursor_type == TIMESTAMP_CURSOR { let duration: Bound<'_, PyAny> = - registry::class(py, RecordId::Duration)?.call_method1("parse", (batch_size,))?; + record_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(®istry::class(py, RecordId::DateValue)?)? - || value.is_instance(®istry::class(py, RecordId::TimestampValue)?)?, - ) + Ok(value.is_instance(&record_class(py, RecordId::DateValue)?)? + || value.is_instance(&record_class(py, RecordId::TimestampValue)?)?) }; if !temporal(&start)? || !temporal(&end)? { return Ok(None); @@ -112,7 +110,7 @@ fn count_batches( if cursor_type != INTEGER_CURSOR { return Ok(None); } - let integer: Bound<'_, PyAny> = registry::class(py, RecordId::IntegerValue)?; + let integer: Bound<'_, PyAny> = record_class(py, RecordId::IntegerValue)?; if !start.is_instance(&integer)? || !end.is_instance(&integer)? { return Ok(None); } 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 29d39adac2..43043a0c2d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/plan_output/display.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/plan_output/display.rs @@ -18,14 +18,16 @@ use sqlbuild_core::plan_display::main::model_materialization_label::model_materi use sqlbuild_core::plan_display::main::model_resource_type::model_resource_type as resource_type; use sqlbuild_core::plan_display::models::{ModelLabelFacts, ParsedBound}; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::imported; 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::construct::{ + enum_from_value, new_record_with, record_class, +}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; const DATETIME_MODULE: &str = "datetime"; @@ -70,7 +72,7 @@ fn cursor_typing<'py>( } else { PyString::new(py, DEFAULT_CURSOR_GRAIN).into_any() }; - let grain: Bound<'py, PyAny> = registry::class(py, RecordId::CursorGrain)?.call1((grain,))?; + let grain: Bound<'py, PyAny> = enum_from_value(py, RecordId::CursorGrain, grain)?; Ok((effective, grain)) } @@ -109,8 +111,9 @@ pub(crate) fn resolve_bounded_cursor_override<'py>( ) else { return Ok(None); }; - call_with( - ®istry::class(py, RecordId::CursorBounds)?, + new_record_with( + py, + RecordId::CursorBounds, &[("start", start), ("end", to_exclusive(&end, &grain)?)], ) .map(Some) @@ -216,7 +219,7 @@ pub(crate) fn resolve_effective_microbatch_batch_size( effective_grain: &str, ) -> Result { effective_microbatch_batch_size(batch_size, effective_grain).map_err(|error| { - registry::class(py, RecordId::CursorGrain) + record_class(py, RecordId::CursorGrain) .and_then(|grain| grain.call1((error.0,))) .err() .unwrap_or_else(|| PyValueError::new_err("unknown cursor grain")) @@ -226,7 +229,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> = native_field(value, "value")?; - if value.is_instance(®istry::class(py, RecordId::TimestampValue)?)? { + if value.is_instance(&record_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 8380e47bf2..8272aa1032 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 @@ -27,9 +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::construct::new_record; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; const SKIP_ACTION: &str = "skip"; const QUERY_CHANGED: &str = "query_changed"; @@ -416,7 +416,7 @@ fn report(arguments: &KeywordArguments<'_>) -> PyResult { let display_options: Bound<'_, PyAny> = { let given: Bound<'_, PyAny> = arguments.value("display_options")?; if given.is_none() { - registry::class(py, RecordId::DisplayOptions)?.call0()? + new_record(py, RecordId::DisplayOptions, (), None)? } 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 5435b9a3fa..748e555e11 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/actions.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/actions.rs @@ -4,10 +4,9 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyRes use pyo3::types::{PyDict, PyDictMethods}; use pyo3::{pyfunction, wrap_pyfunction}; -use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::construct::{enum_member, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; const INCREMENTAL_MATERIALIZATION: &str = "incremental"; const CREATE_TABLE_ACTION: &str = "create_table"; @@ -20,9 +19,7 @@ pub(crate) fn resolve_model_actions<'py>( changes: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = scope.py(); - let model_kind: Bound<'py, PyAny> = - registry::class(py, RecordId::CompiledResourceType)?.getattr("MODEL")?; - let action_class: Bound<'py, PyAny> = registry::class(py, RecordId::ResolvedModelAction)?; + let model_kind: Bound<'py, PyAny> = enum_member(py, RecordId::CompiledResourceType, "MODEL")?; let selected: Bound<'py, PyAny> = native_field(scope, "selected_keys")?; let models_by_name: Bound<'py, PyAny> = native_field(scope, "models_by_name")?; let model_changes: Bound<'py, PyAny> = native_field(changes, "models")?; @@ -40,11 +37,16 @@ pub(crate) fn resolve_model_actions<'py>( let backfill: Bound<'py, PyAny> = native_field(&change, "backfill")?; resolved.set_item( name, - call_with(&action_class, &[("change", change), ("backfill", backfill)])?, + new_record_with( + py, + RecordId::ResolvedModelAction, + &[("change", change), ("backfill", backfill)], + )?, )?; } - call_with( - ®istry::class(py, RecordId::PlannerResolvedActions)?, + new_record_with( + 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 60bf12cb77..1dd367fdf9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/detect.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/detect.rs @@ -4,6 +4,7 @@ use crate::bindings::_helpers::analysis::model_reads; use crate::bindings::_helpers::contracts::spec_resolution::matching_dynamic_families; use crate::bindings::_helpers::model_config::config_values::config_str as authored_config_str; use crate::bindings::_helpers::planner_changes::results::native_enum; +use crate::bindings::_helpers::records::construct::{enum_member, new_record_with}; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyString, PyTuple}; use pyo3::{IntoPyObject, pyfunction, wrap_pyfunction}; @@ -45,7 +46,6 @@ use crate::bindings::_helpers::planner_identity::metadata::{ }; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::type_system::equality::types_equal as native_types_equal; const CHANGES_LOGGER: &str = "sqlbuild.planner.changes"; @@ -100,7 +100,6 @@ pub(crate) fn detect_changes<'py>( let models: Bound<'py, PyDict> = scope_model_changes((project, scope, snapshot), None, expected)?; let functions: Bound<'py, PyDict> = PyDict::new(py); - let result_class: Bound<'py, PyAny> = registry::class(py, RecordId::FunctionChangeResult)?; let selected: Bound<'py, PyAny> = native_field(scope, "selected_keys")?; let recorded: Bound<'py, PyAny> = native_field(snapshot, "fingerprints")?.getattr("functions")?; @@ -146,10 +145,14 @@ pub(crate) fn detect_changes<'py>( }; arguments.push(("reason", planner_enum(py, RecordId::PlanReason, reason)?)); } - functions.set_item(name, call_with(&result_class, &arguments)?)?; + functions.set_item( + name, + new_record_with(py, RecordId::FunctionChangeResult, &arguments)?, + )?; } - call_with( - ®istry::class(py, RecordId::PlannerChangeResults)?, + new_record_with( + py, + RecordId::PlannerChangeResults, &[ ("models", models.into_any()), ("functions", functions.into_any()), @@ -273,8 +276,7 @@ pub(crate) fn scope_model_changes<'py>( function_local_hashes, hook_version_hashes: hook_version_hashes.into_any(), }; - let model_kind: Bound<'py, PyAny> = - registry::class(py, RecordId::CompiledResourceType)?.getattr("MODEL")?; + let model_kind: Bound<'py, PyAny> = enum_member(py, RecordId::CompiledResourceType, "MODEL")?; let selected: Bound<'py, PyAny> = native_field(scope, "selected_keys")?; let models_by_name: Bound<'py, PyAny> = native_field(scope, "models_by_name")?; let changes: Bound<'py, PyDict> = PyDict::new(py); @@ -746,8 +748,9 @@ impl<'py> ChangeResult<'py> { .into_pyobject(py) .map(|flag| flag.to_owned().into_any()) }; - call_with( - ®istry::class(py, RecordId::ChangeDetectionResult)?, + new_record_with( + 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 2f7aa8f553..b53ef7a4bd 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/renames.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/renames.rs @@ -1,6 +1,7 @@ //! Model renames that downstream `__ref` names may have been rewritten for. use crate::bindings::_helpers::fingerprints::query_hashes::compute_query_hash; +use crate::bindings::_helpers::records::construct::{enum_member, new_record_with}; use std::collections::BTreeSet; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; @@ -19,7 +20,6 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ use crate::bindings::_helpers::planner_identity::hashing::model_query_hash; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; const GENERIC_DIALECT: &str = "generic"; const FINAL_NEWLINE: &str = "\n"; @@ -182,14 +182,14 @@ pub(crate) fn plan_reference_renames<'py>( let target_name: Bound<'py, PyAny> = runtime .getattr("project")? .getattr("effective_target_name")?; - 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?; let (new_name, origin_name) = (item.get_item(0)?, item.get_item(1)?); if new_name.ne(&origin_name)? { - renames.push(call_with( - &rename_class, + renames.push(new_record_with( + py, + RecordId::ReferenceRename, &[("new_name", new_name), ("origin_name", origin_name)], )?); } @@ -203,8 +203,9 @@ pub(crate) fn plan_reference_renames<'py>( && !origin.is_none() && origin.ne(&destination)? { - renames.push(call_with( - &rename_class, + renames.push(new_record_with( + py, + RecordId::ReferenceRename, &[ ("new_name", destination), ("origin_name", origin), @@ -232,8 +233,7 @@ fn changed_referencing_query( } let recorded_models: Bound<'_, PyAny> = native_field(snapshot, "fingerprints")?.getattr("models")?; - let ref_kind: Bound<'_, PyAny> = - registry::class(py, RecordId::SqlReferenceKind)?.getattr("REF")?; + let ref_kind: Bound<'_, PyAny> = enum_member(py, RecordId::SqlReferenceKind, "REF")?; let dialect: Option = native_field(snapshot, "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 bd2d0137ba..576dead32b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/results.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/results.rs @@ -9,7 +9,7 @@ use sqlbuild_analysis::planner_changes::models::{ Backfill, BackfillAction, ChangeKind, SchemaChangeKind, SchemaColumnSource, SchemaFindingRow, }; -use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::construct::{new_record, new_record_with}; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -29,7 +29,7 @@ pub(crate) fn planner_enum<'py>( name: RecordId, value: &str, ) -> PyResult> { - registry::class(py, name)?.call1((value,)) + new_record(py, name, (value,), None) } /// A member of a natively declared planner enumeration, from its value. @@ -38,7 +38,7 @@ pub(crate) fn native_enum<'py>( name: RecordId, value: &str, ) -> PyResult> { - registry::class(py, name)?.call1((value,)) + new_record(py, name, (value,), None) } /// `BackfillResult(action=..., duration=...)`. @@ -51,8 +51,9 @@ pub(crate) fn backfill_result<'py>( BackfillAction::Bounded => "bounded", BackfillAction::ForwardOnly => "forward", }; - call_with( - ®istry::class(py, RecordId::BackfillResult)?, + new_record_with( + py, + RecordId::BackfillResult, &[ ( "action", @@ -83,7 +84,6 @@ pub(crate) fn schema_findings<'py>( py: Python<'py>, findings: &[SchemaFindingRow], ) -> PyResult> { - 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 { @@ -95,8 +95,9 @@ pub(crate) fn schema_findings<'py>( SchemaColumnSource::Yml => "yml", SchemaColumnSource::SqlAnalysis => "sql_analysis", }; - items.push(call_with( - &class, + items.push(new_record_with( + py, + RecordId::SchemaFinding, &[ ("kind", native_enum(py, RecordId::SchemaChangeKind, kind)?), ( 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 fa549ebded..4ba7000e0a 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 @@ -23,9 +23,9 @@ use crate::bindings::_helpers::planner_resolution::orchestration::{Resolution, r use crate::bindings::_helpers::planner_selection::selectors::resolve_graph_selection; use crate::bindings::_helpers::planner_strategy::strategy::materialization_of; use crate::bindings::_helpers::planner_warehouse::source_columns::gather_source_columns; +use crate::bindings::_helpers::records::construct::new_record; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use pyo3::PyRef; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyBool, PyDict, PyDictMethods, PyFrozenSet, PySet, PySetMethods, PyTuple}; @@ -272,7 +272,9 @@ fn clone_source_entries<'py>( let qualified = adapter .getattr("render_qualified_name")? .call((), Some(&kwargs))?; - let destination = registry::class(py, RecordId::CompiledRelationLocation)?.call( + let destination = new_record( + py, + RecordId::CompiledRelationLocation, (), Some(&{ let location = PyDict::new(py); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/contexts.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/contexts.rs index 44111a26d4..58595655e7 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/contexts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/contexts.rs @@ -1,6 +1,6 @@ //! Relation, model-plan and entry-build contexts: built from keyword arguments and read by plan entry construction. -use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyResult, Python}; +use pyo3::prelude::{Bound, Py, PyAny, PyResult, Python}; use pyo3::pymethods; use pyo3::types::{PyDict, PyFrozenSet}; @@ -8,8 +8,8 @@ use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; use crate::bindings::_helpers::planner_entries::models::{ ModelPlanContext, PlanEntryBuildInputs, PlannerRelationsContext, }; +use crate::bindings::_helpers::records::construct::enum_member; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; /// The argument `name`, or `default()` when it was not passed. fn value_or<'py>( @@ -167,5 +167,5 @@ impl PlanEntryBuildInputs { /// The microbatch limit action used when a project configures none. pub(crate) fn default_microbatch_limit_action(py: Python<'_>) -> PyResult> { - registry::class(py, RecordId::MicrobatchLimitAction)?.getattr("ERROR") + enum_member(py, RecordId::MicrobatchLimitAction, "ERROR") } 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 940c305f91..c0acf2c362 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 @@ -31,9 +31,9 @@ use crate::bindings::_helpers::planner_phases::scopes::resolve_planner_scopes; use crate::bindings::_helpers::planner_phases::warehouse_state::gather_planner_warehouse_state; use crate::bindings::_helpers::planner_resolution::cursor_overrides::type_cursor_overrides; use crate::bindings::_helpers::planner_warehouse::source_freshness::build_planner_source_freshness_result; +use crate::bindings::_helpers::records::construct::enum_member; use crate::bindings::_helpers::records::fields::field as native_field; 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}; @@ -53,7 +53,7 @@ fn scopes_and_cursor_overrides<'py>( )?; let selected = class_field(&scopes, |s: &PlannerScopeResolution| &s.selected_scope)?; let models_by_name = native_field(&selected, "models_by_name")?; - let model_type = registry::class(py, RecordId::CompiledResourceType)?.getattr("MODEL")?; + let model_type = enum_member(py, RecordId::CompiledResourceType, "MODEL")?; let mut models: Vec> = Vec::new(); for key in native_field(&selected, "selected_keys")?.try_iter()? { let key = key?; 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 a79330227f..08e014e64f 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 @@ -41,9 +41,9 @@ use crate::bindings::_helpers::planner_strategy::strategy::{ materialization_of, resolve_model_plan_action, }; use crate::bindings::_helpers::planner_strategy::values::text; +use crate::bindings::_helpers::records::construct::enum_member; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; -use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::semantic_shapes::source_bindings::source_binding_diagnostics; use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods}; @@ -224,7 +224,7 @@ fn check_binding_diagnostics<'py>( selected_keys: &Bound<'py, PyAny>, ) -> PyResult<()> { let py = project.py(); - let sql_test = registry::class(py, RecordId::CompiledResourceType)?.getattr("SQL_TEST")?; + let sql_test = enum_member(py, RecordId::CompiledResourceType, "SQL_TEST")?; let mut errors: Vec> = Vec::new(); for diagnostic in native_field(project, "diagnostics")?.try_iter()? { let diagnostic = diagnostic?; @@ -363,7 +363,7 @@ fn check_microbatch_concurrency<'py>( ("model_name", none(py)), ( "severity", - registry::class(py, RecordId::WarningSeverity)?.getattr("WARNING")?, + enum_member(py, RecordId::WarningSeverity, "WARNING")?, ), ("code", string(py, MICROBATCH_COMPLEXITY_CODE)), ( @@ -454,7 +454,7 @@ pub(crate) fn plan_entries<'py>( )?; let sql_analysis_enabled = native_field(&settings, "sql_analysis")?.is_truthy()?; let skip = planner_type(py, RecordId::PlanAction, "SKIP")?; - let microbatch = registry::class(py, RecordId::IncrementalMode)?.getattr("MICROBATCH")?; + let microbatch = enum_member(py, RecordId::IncrementalMode, "MICROBATCH")?; let mut entries: Vec> = Vec::new(); let mut warnings: Vec> = Vec::new(); for key in native_field(&scope, "execution_order")?.try_iter()? { 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 0789fc370c..0be95249c6 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 @@ -14,9 +14,9 @@ use crate::bindings::_helpers::planner_scope::sources::build_source_load_entries use crate::bindings::_helpers::planner_strategy::audit_entries::build_selected_audit_entries; use crate::bindings::_helpers::planner_strategy::fixture_context::planning_context; use crate::bindings::_helpers::planner_strategy::model_entries::extract_seed_columns; +use crate::bindings::_helpers::records::construct::record_class; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; -use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sql_test_glue::models::SqlTestAssembly; use crate::bindings::_helpers::sql_test_glue::plan_assembly::assemble_sql_test_plans; use crate::bindings::_helpers::sql_test_glue::plan_driver::resolve_sql_test_model_chains; @@ -196,7 +196,7 @@ fn hook_provider_usages<'py>( ) -> PyResult>> { let py = entry.py(); let mut usages: Vec> = Vec::new(); - let python_hook = registry::class(py, RecordId::PythonHookEntry)?; + let python_hook = record_class(py, RecordId::PythonHookEntry)?; let mut hooks: Vec> = Vec::new(); for field in ["pre_hooks", "post_hooks"] { let hook_entry = entry.getattr(field)?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/test_planning.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/test_planning.rs index ed2e2d34d0..26487ca4f9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/test_planning.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/test_planning.rs @@ -14,13 +14,11 @@ use pyo3::types::PyDict; use pyo3::{pyclass, pyfunction, pymethods, wrap_pyfunction}; use crate::bindings::_helpers::boundary::panics::compiler_guard; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported}; use crate::bindings::_helpers::native_threads::exit_signal::signal_interpreter_exit; use crate::bindings::_helpers::planner_entries::plan_output::plan_selected_sql_tests; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; const TEST_PLANNING_THREAD: &str = "sqlbuild-test-planning"; /// How long interpreter exit waits for SQL test planning threads still running native code. @@ -180,8 +178,9 @@ pub(crate) fn start_planning( enabled: bool, ) -> PyResult> { if !enabled { - let empty = call_with( - ®istry::class(py, RecordId::PlannedSqlTests)?, + let empty = new_record_with( + py, + RecordId::PlannedSqlTests, &[ ("selected_keys", request.selected_keys.bind(py).clone()), ( 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 2e0bdc8483..0ef05e4c46 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/values.rs @@ -1,6 +1,7 @@ //! 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::records::construct::{enum_member, new_record_with}; use crate::bindings::_helpers::records::ids::{ErrorId, NativeItem, RecordId}; use crate::bindings::_helpers::records::registry; use pyo3::IntoPyObject; @@ -25,7 +26,7 @@ pub(super) fn planner_model<'py>( name: RecordId, pairs: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - call_with(®istry::class(py, name)?, pairs) + new_record_with(py, name, pairs) } /// One member of a planner enumeration. @@ -34,7 +35,7 @@ pub(super) fn planner_type<'py>( class: RecordId, member: &str, ) -> PyResult> { - registry::member(py, class, member) + enum_member(py, class, member) } /// A Python string. 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 2343090563..b21c2a67af 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/graph.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/graph.rs @@ -22,10 +22,11 @@ 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}; +use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; 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::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -373,11 +374,11 @@ pub(crate) fn check_buildability<'py>( Some(name_set(&deferred_relations)?) }, }; - 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( - &entry_class, + entries.push(new_record_with( + py, + RecordId::MissingUpstream, &[ ("key", objects.object(&missing.key)), ( 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 2bfa79d714..faa82435df 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/identities.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/identities.rs @@ -15,18 +15,16 @@ use sqlbuild_analysis::planner_identity::main::model_local_identity_hash::model_ use sqlbuild_analysis::planner_identity::models::{GraphKey, IdentityGraph, IdentityNode}; use crate::bindings::_helpers::analysis::model_reads; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, 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::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; const DATACLASSES_MODULE: &str = "dataclasses"; @@ -197,8 +195,9 @@ pub(crate) fn direct_model_version_identities<'py>( version_hashes.set_item(&name, model_hash)?; } } - call_with( - ®istry::class(py, RecordId::DirectModelVersionIdentities)?, + new_record_with( + 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 99901a016b..c9c2988faf 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/staleness.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/staleness.rs @@ -1,6 +1,7 @@ //! Warnings for selected models stale through changed upstreams outside the run set. use crate::bindings::_helpers::planner_changes::results::native_enum; +use crate::bindings::_helpers::records::construct::new_record_with; use std::collections::{HashMap, HashSet}; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; @@ -16,12 +17,11 @@ use sqlbuild_analysis::planner_identity::models::{ GraphKey, StaleWarning, StalenessGraph, StalenessNames, }; -use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; use crate::bindings::_helpers::planner_changes::detect::{ModelPass, model_change}; use crate::bindings::_helpers::planner_identity::hashing::graph_key; use crate::bindings::_helpers::records::fields::field as native_field; 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] = [ @@ -147,7 +147,6 @@ fn plan_warnings<'py>( py: Python<'py>, warnings: Vec, ) -> PyResult> { - 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 { @@ -156,8 +155,9 @@ fn plan_warnings<'py>( "upstream(s)", &warning.trigger_names, ); - rendered.push(call_with( - &warning_class, + rendered.push(new_record_with( + py, + RecordId::PlanWarning, &[ ( "model_name", @@ -261,12 +261,11 @@ fn classify_selection_staleness_warnings<'py>( sources: name_set(&native_field(graph, "changed_source_names")?)?, }, }; - let warning_class: Bound<'py, PyAny> = - registry::class(py, RecordId::SelectionStalenessWarning)?; let mut warnings: Vec> = Vec::new(); for warning in classify_selection_staleness(&staleness) { - warnings.push(call_with( - &warning_class, + warnings.push(new_record_with( + py, + RecordId::SelectionStalenessWarning, &[ ( "model_name", 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 5385aa4854..f9343537a6 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 @@ -14,9 +14,9 @@ use crate::bindings::_helpers::planner_migrations::constants::{AUTOMATIC, DONE}; use crate::bindings::_helpers::planner_migrations::models::{CheckedColumns, ColumnCandidate}; use crate::bindings::_helpers::planner_phases::objects::object; use crate::bindings::_helpers::planner_phases::runtime::runtime_field; +use crate::bindings::_helpers::records::construct::{enum_from_value, enum_member}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::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}; @@ -96,8 +96,7 @@ pub(super) fn decide<'py>( (origin.is_some(), has_destination), outputs.contains(&origin_key), ); - let decision = - registry::class(py, RecordId::ColumnMigrationDecision)?.call1((decision_name,))?; + let decision = enum_from_value(py, RecordId::ColumnMigrationDecision, decision_name)?; if discovery == AUTOMATIC && native_field(&decision, "blocks_build")?.is_truthy()? { continue; } @@ -119,7 +118,7 @@ pub(super) fn decide<'py>( ("destination_column", destination), ( "discovery", - registry::class(py, RecordId::MigrationDiscovery)?.call1((discovery,))?, + enum_from_value(py, RecordId::MigrationDiscovery, discovery)?, ), ("decision", decision), ( @@ -206,8 +205,7 @@ pub(super) fn checked<'py>( &[ ( "decision", - registry::class(py, RecordId::ColumnMigrationDecision)? - .getattr("UNSUPPORTED")?, + enum_member(py, RecordId::ColumnMigrationDecision, "UNSUPPORTED")?, ), ("message", reason.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 6bc3a383e4..b7bd7438f6 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 @@ -5,6 +5,7 @@ use crate::bindings::_helpers::compile_driver::python_calls::{call_with, importe use crate::bindings::_helpers::model_config::config_values::config_str as authored_config_str; use crate::bindings::_helpers::planner_identity::hashing::model_definition_hash; use crate::bindings::_helpers::planner_phases::objects::object; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeItem, RecordId}; use crate::bindings::_helpers::records::registry; @@ -35,7 +36,7 @@ pub(super) fn model<'py>( name: RecordId, fields: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - call_with(®istry::class(py, name)?, fields) + new_record_with(py, name, fields) } pub(super) fn native<'py>( 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 18abe9e14e..b274979dc9 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 @@ -6,9 +6,9 @@ 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::construct::enum_member; use crate::bindings::_helpers::records::fields::field as native_field; 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; @@ -28,11 +28,11 @@ pub(super) fn warning<'py>( ("model_name", native_field(entry, "model_name")?), ( "severity", - registry::class(py, RecordId::WarningSeverity)?.getattr(if error { - "ERROR" - } else { - "WARNING" - })?, + enum_member( + py, + RecordId::WarningSeverity, + if error { "ERROR" } else { "WARNING" }, + )?, ), ("message", text(py, message)), ("code", text(py, code)), 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 292a8a600d..91a92f9499 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/columns.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/columns.rs @@ -11,10 +11,10 @@ use sqlbuild_analysis::planner_migrations::main::{ }; use sqlbuild_analysis::planner_migrations::models::Projection; -use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; 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,8 +119,9 @@ pub(crate) fn rename_hints<'py>( ); let mut result: Vec> = Vec::new(); for hint in hints { - result.push(call_with( - ®istry::class(py, RecordId::ColumnRenameHint)?, + result.push(new_record_with( + 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 6ac4996040..1e583e79fd 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/compatibility.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/compatibility.rs @@ -2,15 +2,15 @@ use crate::bindings::_helpers::analysis::model_reads; use crate::bindings::_helpers::analysis::model_reads::inferred_rows; +use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; use crate::bindings::_helpers::compile_driver::python_calls::keywords; -use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::planner_changes::detect::detect_model_changes; use crate::bindings::_helpers::planner_migrations::models::{ CompatibilityCheck, MigrationCompatibilityResult, }; +use crate::bindings::_helpers::records::construct::{enum_from_value, new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; 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, PyDictMethods, PyTuple, PyTupleMethods}; @@ -43,15 +43,16 @@ fn blocking<'py>( .filter(|value| !value.is_empty()) .unwrap_or_else(|| "append_new_columns".to_owned()) }; - registry::class( + new_record( py, if snapshot { RecordId::SnapshotSchemaChangePolicy } else { RecordId::OnSchemaChange }, - )? - .call1((&policy,))?; + (&policy,), + None, + )?; let mut findings: Vec = Vec::new(); for finding in findings_in.try_iter()? { let finding = finding?; @@ -137,8 +138,9 @@ pub(super) fn check_migration_compatibility<'py>( model_reads::name_object(&model)?, check.origin_columns.clone(), )?; - let snapshot = call_with( - ®istry::class(py, RecordId::WarehouseSnapshot)?, + let snapshot = new_record_with( + py, + RecordId::WarehouseSnapshot, &[ ("existing_relations", relations.into_any()), ("existing_columns", columns.into_any()), @@ -198,7 +200,7 @@ fn result<'py>( findings: Bound<'py, PyTuple>, ) -> PyResult> { Ok(MigrationCompatibilityResult { - status: registry::class(py, RecordId::MigrationCompatibility)?.call1((status,))?, + status: enum_from_value(py, RecordId::MigrationCompatibility, status)?, findings: findings.into_any(), }) } 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 cdccb3cc10..07a436836b 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 @@ -9,9 +9,9 @@ use crate::bindings::_helpers::planner_migrations::discovery_events::{ use crate::bindings::_helpers::planner_migrations::models::{ ModelMigrationDeclaration, ModelMigrationRequest, }; +use crate::bindings::_helpers::records::construct::enum_member; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::types::{ PyDict, PyDictMethods, PyFrozenSet, PyListMethods, PySet, PySetMethods, PyString, PyTuple, @@ -145,8 +145,7 @@ pub(super) fn automatic<'py>( if let Some((location, origin_model)) = origin { requests.push(ModelMigrationRequest { model: item.clone(), - discovery: registry::class(py, RecordId::MigrationDiscovery)? - .getattr("AUTOMATIC")?, + discovery: enum_member(py, RecordId::MigrationDiscovery, "AUTOMATIC")?, raw_origin: None, origin_location: Some(location), origin_model, @@ -179,7 +178,7 @@ pub(super) fn warnings<'py>( if selected.contains(&name)? { warnings.push(native(py, RecordId::PlanWarning, &[ ("model_name", PyString::new(py, &name).into_any()), - ("severity", registry::class(py, RecordId::WarningSeverity)?.getattr("WARNING")?), + ("severity", enum_member(py, RecordId::WarningSeverity, "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 1837c63193..67a67d6934 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 @@ -11,6 +11,7 @@ use crate::bindings::_helpers::planner_migrations::model_handover::{ use crate::bindings::_helpers::planner_migrations::models::{ CompatibilityCheck, MigrationDecisionRequest, }; +use crate::bindings::_helpers::records::construct::{enum_from_value, enum_member}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -40,7 +41,7 @@ pub(super) fn decision<'py>(entry: &Bound<'py, PyAny>, name: &str) -> PyResult( ("discovery", request.discovery.clone()), ( "decision", - registry::class(py, RecordId::MigrationDecision)?.getattr("MIGRATE")?, + enum_member(py, RecordId::MigrationDecision, "MIGRATE")?, ), ( "compatibility", - registry::class(py, RecordId::MigrationCompatibility)?.getattr("NOT_CHECKED")?, + enum_member(py, RecordId::MigrationCompatibility, "NOT_CHECKED")?, ), ("origin_model", origin_model), ("origin", origin.clone()), 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 32e4de77cc..4716417459 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 @@ -14,9 +14,9 @@ use crate::bindings::_helpers::planner_migrations::planning_origin::{ }; use crate::bindings::_helpers::planner_phases::objects::object; use crate::bindings::_helpers::planner_phases::runtime::runtime_field; +use crate::bindings::_helpers::records::construct::{enum_member, new_record}; use crate::bindings::_helpers::records::fields::field as native_field; 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, PySet, PySetMethods, PyString, PyTuple, @@ -32,7 +32,7 @@ fn declared<'py>(item: &Bound<'py, PyAny>) -> PyResult( )?; let project_config = runtime_field(&runtime, |runtime| &runtime.project_config)?; let snapshots_config = if project_config.is_none() { - registry::class(py, RecordId::SnapshotsConfig)?.call0()? + new_record(py, RecordId::SnapshotsConfig, (), None)? } else { native_field(&project_config, "snapshots")? }; 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 86f7b37a68..70cdd3f85d 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 @@ -12,9 +12,9 @@ use crate::bindings::_helpers::planner_migrations::old_names::declared_column_al use crate::bindings::_helpers::planner_migrations::old_names::old_name_view_skip_reason; use crate::bindings::_helpers::planner_phases::objects::object; use crate::bindings::_helpers::planner_phases::runtime::runtime_field; +use crate::bindings::_helpers::records::construct::{enum_member, new_record, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::types::{PyBool, PyList, PyTuple}; @@ -29,7 +29,7 @@ impl<'py> OldNamePlanner<'py> { let py = runtime.py(); let project_config = runtime_field(&runtime, |runtime| &runtime.project_config)?; let config = if project_config.is_none() { - registry::class(py, RecordId::MigrationsConfig)?.call0()? + new_record(py, RecordId::MigrationsConfig, (), None)? } else { native_field(&project_config, "migrations")? }; @@ -303,7 +303,7 @@ impl<'py> OldNamePlanner<'py> { } } let retention = native_field(history, "required")?.getattr("view_retention")?; - let duration = registry::class(py, RecordId::Duration)?.call_method1( + let duration = record_class(py, RecordId::Duration)?.call_method1( "parse", (if retention.is_truthy()? { retention.clone() @@ -387,5 +387,5 @@ impl<'py> OldNamePlanner<'py> { } fn action<'py>(runtime: &Bound<'py, PyAny>, name: &str) -> PyResult> { - registry::class(runtime.py(), RecordId::OldNameViewAction)?.getattr(name) + enum_member(runtime.py(), RecordId::OldNameViewAction, 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 6eff236782..8a0ca29733 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 @@ -4,9 +4,9 @@ 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::construct::{new_record_with, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; 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}; @@ -30,10 +30,7 @@ pub(super) fn location<'py>( &fields, )?; fields.push(("qualified_name", qualified)); - call_with( - ®istry::class(runtime.py(), RecordId::CompiledRelationLocation)?, - &fields, - ) + new_record_with(runtime.py(), RecordId::CompiledRelationLocation, &fields) } pub(super) fn stores_history(item: &Bound<'_, PyAny>) -> PyResult { @@ -53,7 +50,7 @@ pub(super) fn expires<'py>( now: &Bound<'py, PyAny>, ) -> PyResult> { let duration = - registry::class(now.py(), RecordId::Duration)?.call_method1("parse", (retention,))?; + record_class(now.py(), RecordId::Duration)?.call_method1("parse", (retention,))?; if duration.is_none() { Ok(now.py().None().into_bound(now.py())) } else { 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 29a95213ef..b5a19c38fc 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 @@ -7,9 +7,9 @@ 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::planner_phases::objects::object; +use crate::bindings::_helpers::records::construct::enum_member; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::references::relations::match_project_relation; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::types::{PyDict, PyDictMethods, PyStringMethods, PyTuple}; @@ -133,7 +133,7 @@ pub(super) fn errors<'py>( ("model_name", py.None().into_bound(py)), ( "severity", - registry::class(py, RecordId::WarningSeverity)?.getattr("ERROR")?, + enum_member(py, RecordId::WarningSeverity, "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 5925e19d1a..111e2ddea5 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,13 +1,13 @@ //! Existing old-name planner models at the native decision boundary. use crate::bindings::_helpers::analysis::model_reads; -use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; 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::construct::{enum_member, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; 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}; @@ -136,13 +136,14 @@ pub(crate) fn old_name_claim_errors<'py>( } let message = claim_message(&name.extract::()?, &view, &arguments.value("now")?)?; - errors.push(call_with( - ®istry::class(py, RecordId::PlanWarning)?, + errors.push(new_record_with( + py, + RecordId::PlanWarning, &[ ("model_name", name.clone()), ( "severity", - registry::class(py, RecordId::WarningSeverity)?.getattr("ERROR")?, + enum_member(py, RecordId::WarningSeverity, "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_execution.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/planning_execution.rs index b507dc28d1..8763786f8b 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 @@ -5,9 +5,9 @@ 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::planner_phases::runtime::runtime_field; +use crate::bindings::_helpers::records::construct::record_class; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::types::PyBool; @@ -25,7 +25,7 @@ fn migration_promotion<'py>( decision: &Bound<'py, PyAny>, ) -> PyResult> { let adapter = runtime_field(runtime, |runtime| &runtime.adapter)?; - let promotion = registry::class(runtime.py(), RecordId::MigrationPromotion)?; + let promotion = record_class(runtime.py(), RecordId::MigrationPromotion)?; if adapter .call_method0("supports_transactional_ddl")? .is_truthy()? 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 a669f815d0..3139915f87 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 @@ -4,6 +4,7 @@ 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::planner_migrations::models::ModelMigrationRequest; use crate::bindings::_helpers::planner_phases::runtime::runtime_field; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -23,10 +24,7 @@ pub(super) fn location<'py>( )?; let mut fields = fields.to_vec(); fields.push(("qualified_name", qualified)); - call_with( - ®istry::class(runtime.py(), RecordId::CompiledRelationLocation)?, - &fields, - ) + new_record_with(runtime.py(), RecordId::CompiledRelationLocation, &fields) } fn named<'py>( 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 1494a50e7f..45c6f43720 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/projections.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/projections.rs @@ -1,10 +1,10 @@ //! Adapt existing Python query shape models to pure migration proofs. -use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; use crate::bindings::_helpers::planner_migrations::errors::ProofError; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; 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,8 +54,9 @@ pub(crate) fn parse_query_shape<'py>( }; let mut projections: Vec> = Vec::new(); for item in shape.projections { - projections.push(call_with( - ®istry::class(py, RecordId::QueryProjection)?, + projections.push(new_record_with( + py, + RecordId::QueryProjection, &[ ("name", item.name.into_pyobject(py)?.into_any()), ("expression", item.expression.into_pyobject(py)?.into_any()), @@ -74,8 +75,9 @@ pub(crate) fn parse_query_shape<'py>( Some(relations) => PyTuple::new(py, relations)?.into_any(), None => py.None().into_bound(py), }; - call_with( - ®istry::class(py, RecordId::QueryShape)?, + new_record_with( + 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_phases/objects.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/objects.rs index e1198f52f9..0a4dea9957 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/objects.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/objects.rs @@ -4,6 +4,7 @@ use crate::bindings::_helpers::compile_driver::python_calls::{call_with, importe use crate::bindings::_helpers::model_config::config_values::config_str as authored_config_str; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::native_host::models::HostId; +use crate::bindings::_helpers::records::construct::{enum_member, new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, NativeItem, RecordId}; use crate::bindings::_helpers::records::registry; @@ -36,7 +37,7 @@ pub(crate) fn planner_model<'py>( name: RecordId, fields: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - call_with(®istry::class(py, name)?, fields) + new_record_with(py, name, fields) } /// `sqlbuild._native.name(**fields)`. @@ -72,7 +73,7 @@ pub(crate) fn native_member<'py>( class: RecordId, value: &str, ) -> PyResult> { - registry::class(py, class)?.call1((value,)) + new_record(py, class, (value,), None) } /// `PlannerInputError(message, code=code, help=help)`. @@ -167,12 +168,13 @@ pub(crate) fn model_key<'py>( py: Python<'py>, name: &Bound<'py, PyAny>, ) -> PyResult> { - call_with( - ®istry::class(py, RecordId::CompiledObjectKey)?, + new_record_with( + py, + RecordId::CompiledObjectKey, &[ ( "resource_type", - registry::class(py, RecordId::CompiledResourceType)?.getattr("MODEL")?, + enum_member(py, RecordId::CompiledResourceType, "MODEL")?, ), ("name", name.clone()), ], 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 1b3478de46..3b97c2ce95 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,7 +1,6 @@ //! Plan output assembly: storage policies, warnings and direct-mode metadata. use crate::bindings::_helpers::analysis::model_reads; -use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::planner_entries::models::PlanOutputInputs; use crate::bindings::_helpers::planner_entries::plan_output::plan_output_fields; @@ -21,9 +20,9 @@ use crate::bindings::_helpers::planner_phases::retention::{plan_retention, plan_ use crate::bindings::_helpers::planner_phases::runtime::runtime_field; use crate::bindings::_helpers::planner_phases::runtime::warehouse_field; use crate::bindings::_helpers::planner_warehouse::source_freshness::source_freshness_unknown_warnings; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::pyfunction; use pyo3::types::{PyBool, PyDict, PyDictMethods, PyTuple}; @@ -63,7 +62,7 @@ impl<'py> PlanOutputFields<'py> { } self.set("metadata", metadata.into_any()); } - call_with(®istry::class(py, RecordId::PlanOutput)?, &self.fields) + new_record_with(py, RecordId::PlanOutput, &self.fields) } } 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 ea3d43c149..ad1dd36132 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/retention.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/retention.rs @@ -15,9 +15,9 @@ use crate::bindings::_helpers::planner_phases::objects::{ planner_model, replace, sorted_names, text, tuple, }; use crate::bindings::_helpers::planner_phases::runtime::runtime_field; +use crate::bindings::_helpers::records::construct::record_class; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use pyo3::IntoPyObject; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::pyfunction; @@ -118,7 +118,7 @@ pub(crate) fn plan_table_types<'py>( .getattr("snapshot")? .getattr("existing_relations")?; let adapter = runtime_field(runtime, |runtime| &runtime.adapter)?; - let materialization = registry::class(py, RecordId::MaterializationType)?; + let materialization = record_class(py, RecordId::MaterializationType)?; let mut entries: Vec<_> = Vec::new(); for model in models { let config = native_field(&model, "config")?; 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 f7ebb34e6a..f407a983f5 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,16 +1,16 @@ //! Snapshot conversion and cursor window policy boundary. +use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; use crate::bindings::_helpers::compile_driver::python_calls::keywords; -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::maximum_safety::apply_maximum_start_policy; use crate::bindings::_helpers::planner_resolution::values::string; +use crate::bindings::_helpers::records::construct::{new_record, new_record_with, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; 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::{IntoPyObject, pyfunction, wrap_pyfunction}; @@ -71,7 +71,7 @@ pub(super) fn duration(value: &Bound<'_, PyAny>) -> PyResult( &["cursor_grain", "maximum_start_policy"], ), )?; - let bounds_class = registry::class(py, RecordId::CursorBounds)?; if args.flag("is_microbatch")? { let sentinel = host::entry(py, ExecutorHost::BoundSentinel)?; - return call_with( - &bounds_class, + return new_record_with( + py, + RecordId::CursorBounds, &[ ("start", native_field(&sentinel, "START")?), ("end", native_field(&sentinel, "END")?), @@ -261,15 +261,16 @@ pub(crate) fn compute_cursor_bounds<'py>( ), ]); let replayed = apply_typed_cursor_replay_policy(py, Some(keywords(py, &replay_args)?))?; - let bounds = call_with( - &bounds_class, + let bounds = new_record_with( + py, + RecordId::CursorBounds, &[("start", replayed), ("end", session.python_scalar(&end)?)], )?; let policy = args.value("maximum_start_policy")?; let policy = if policy.is_truthy()? { policy } else { - registry::class(py, RecordId::MaximumStartPolicyInputs)?.call0()? + new_record(py, RecordId::MaximumStartPolicyInputs, (), None)? }; let mut policy_args = vec![ ("bounds", bounds), 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 7326f8870f..4b78c970fb 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 @@ -27,6 +27,7 @@ use crate::bindings::_helpers::planner_resolution::cursor_windows::without_desti use crate::bindings::_helpers::planner_resolution::future_safety::resolve_future_cursor_safety; use crate::bindings::_helpers::planner_resolution::models::ModelSqlRequest; use crate::bindings::_helpers::planner_resolution::values::string; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -45,8 +46,9 @@ fn config<'py>(model: &Bound<'py, PyAny>, key: &str) -> PyResult) -> PyResult> { let sentinel = host::entry(py, ExecutorHost::BoundSentinel)?; - call_with( - ®istry::class(py, RecordId::CursorBounds)?, + new_record_with( + py, + RecordId::CursorBounds, &[ ("start", native_field(&sentinel, "START")?), ("end", native_field(&sentinel, "END")?), @@ -202,8 +204,9 @@ pub(crate) fn compute_model_cursor_bounds<'py>( } else { py.None().into_bound(py) }; - let policy = call_with( - ®istry::class(py, RecordId::MaximumStartPolicyInputs)?, + let policy = new_record_with( + py, + RecordId::MaximumStartPolicyInputs, &[ ("config", start_config), ( 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 585e1fd5c5..09726eecf2 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 @@ -4,6 +4,7 @@ use crate::bindings::_helpers::analysis::model_reads; use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::model_config::config_values::config_str as authored_config_str; use crate::bindings::_helpers::planner_resolution::values::{method, string}; +use crate::bindings::_helpers::records::construct::enum_from_value; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -81,7 +82,7 @@ pub(crate) fn type_cursor_overrides<'py>( ("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 = registry::class(py, RecordId::CursorType)?.call1((kind,))?; + let kind = enum_from_value(py, RecordId::CursorType, 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 4756bff2a7..64622d1b47 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,9 +1,9 @@ //! Model-level cursor safety policy overrides over project configuration. use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::construct::{enum_from_value, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; 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}; @@ -38,7 +38,7 @@ fn resolve_policy<'py>( fields: &PolicyFields, ) -> PyResult> { let py = model.py(); - let class = registry::class(py, fields.class)?; + let class = record_class(py, fields.class)?; let configured = if project_config.is_truthy()? { project_config.clone() } else { @@ -57,7 +57,7 @@ fn resolve_policy<'py>( } let mut action = native_field(&configured, "action")?; if raw_action.is_instance_of::() { - action = registry::class(py, RecordId::FutureCursorAction)?.call1((raw_action,))?; + action = enum_from_value(py, RecordId::FutureCursorAction, 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 318f829f48..96d41b9216 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 @@ -29,6 +29,7 @@ use crate::bindings::_helpers::planner_resolution::cursor_windows::without_desti use crate::bindings::_helpers::planner_resolution::future_safety::resolve_future_cursor_safety; use crate::bindings::_helpers::planner_resolution::values::string; use crate::bindings::_helpers::planner_strategy::model_entries::resolve_microbatch_lookback; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -171,8 +172,9 @@ fn timestamp_value<'py>(clock: &Bound<'py, PyAny>, scalar: &Scalar) -> PyResult< let ScalarValue::Timestamp(timestamp) = &scalar.value else { return Ok(clock.py().None().into_bound(clock.py())); }; - call_with( - ®istry::class(clock.py(), RecordId::TimestampValue)?, + new_record_with( + clock.py(), + RecordId::TimestampValue, &[("value", replaced(clock, ×tamp.local, timestamp.fold)?)], ) } @@ -223,8 +225,9 @@ fn rolling_window_bounds<'py>( handle: None, }; let (start, end) = session.checked(rolling_window(&clock, &span, grain))?; - let mut bounds = call_with( - ®istry::class(py, RecordId::CursorBounds)?, + let mut bounds = new_record_with( + py, + RecordId::CursorBounds, &[ ("start", timestamp_value(&moment, &start)?), ("end", timestamp_value(&moment, &end)?), @@ -313,8 +316,9 @@ fn snapshot_bounds<'py>( request: BoundsRequest<'py>, ) -> PyResult> { let py = model.py(); - let policy = call_with( - ®istry::class(py, RecordId::MaximumStartPolicyInputs)?, + let policy = new_record_with( + py, + RecordId::MaximumStartPolicyInputs, &[ ("config", inputs.policies.1.clone()), ("invocation_time", inputs.invocation_time.clone()), 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 cde6947fd6..58384629be 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,6 +3,7 @@ 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::construct::{new_record_with, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -44,10 +45,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(®istry::class(py, RecordId::DateValue)?)? - || value.is_instance(®istry::class(py, RecordId::TimestampValue)?)?, - ) + Ok(value.is_instance(&record_class(py, RecordId::DateValue)?)? + || value.is_instance(&record_class(py, RecordId::TimestampValue)?)?) } pub(super) fn utc_clock<'py>(clock: &Bound<'py, PyAny>) -> PyResult> { @@ -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(®istry::class(py, RecordId::DateValue)?)?; + let date_only = discovered.is_instance(&record_class(py, RecordId::DateValue)?)?; if date_only || native_field(discovered, "value")? .getattr("tzinfo")? @@ -74,10 +73,7 @@ pub(super) fn horizon<'py>( &[("tzinfo", py.None().into_bound(py))], )?; } - let clock = call_with( - ®istry::class(py, RecordId::TimestampValue)?, - &[("value", clock)], - )?; + let clock = new_record_with(py, RecordId::TimestampValue, &[("value", clock)])?; let grain = if grain.is_truthy()? { session.grain(grain)?.unwrap_or(Grain::Second) } else { 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 905ac9abb2..de11c1d389 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,11 +1,10 @@ //! Prominent warnings for applied future-end and automatic-start cursor safety. -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::construct::{enum_member, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; 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}; @@ -80,13 +79,14 @@ pub(crate) fn with_cursor_safety_warnings<'py>( (maximum_start_message(bounds)?, START_CAPPED_CODE), ] { let Some(message) = message else { continue }; - all.push(call_with( - ®istry::class(py, RecordId::PlanWarning)?, + all.push(new_record_with( + py, + RecordId::PlanWarning, &[ ("model_name", model_name.clone()), ( "severity", - registry::class(py, RecordId::WarningSeverity)?.getattr("WARNING")?, + enum_member(py, RecordId::WarningSeverity, "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 bf4c2be6ea..8c4581143f 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,6 +4,7 @@ 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::construct::{new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -22,14 +23,14 @@ fn decide_empty_cursor_inputs<'py>( has_window_start: bool, ) -> PyResult> { let py = empty_input_names.py(); - 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) { - return class.call0(); + return new_record(py, RecordId::EmptyCursorInputDecision, (), None); } - call_with( - &class, + new_record_with( + py, + RecordId::EmptyCursorInputDecision, &[ ("input_names", empty_input_names.clone()), ( @@ -49,7 +50,12 @@ pub(crate) fn cursor_inputs_without_rows<'py>( .getattr("unreadable_input_names")? .is_truthy()? { - return registry::class(cursor_snapshot.py(), RecordId::EmptyCursorInputDecision)?.call0(); + return new_record( + cursor_snapshot.py(), + RecordId::EmptyCursorInputDecision, + (), + None, + ); } let rows = native_field(cursor_snapshot, "upstream_maxes")?.is_truthy()? || cursor_snapshot @@ -94,13 +100,15 @@ pub(crate) fn empty_cursor_window<'py>( }; let mut bound = parse_value(&raw, &kind)?; if cursor_start.is_none() && !kind.eq("integer")? { - bound = call_with( - ®istry::class(py, RecordId::TimestampValue)?, + bound = new_record_with( + py, + RecordId::TimestampValue, &[("value", native_field(&bound, "value")?)], )?; } - call_with( - ®istry::class(py, RecordId::CursorBounds)?, + new_record_with( + py, + RecordId::CursorBounds, &[("start", bound.clone()), ("end", bound)], ) } 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 9a1d12aa84..884f3466ae 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 @@ -5,9 +5,9 @@ use crate::bindings::_helpers::cursor::conversion::CursorSession; use crate::bindings::_helpers::planner_resolution::cursor_safety::{ horizon, replace, safety_duration, safety_error, timestamp_bound, utc_clock, }; +use crate::bindings::_helpers::records::construct::{new_record_with, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; 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}; @@ -120,15 +120,16 @@ pub(crate) fn resolve_future_cursor_safety<'py>( Ok(py.None().into_bound(py)) } }; - let safety = call_with( - ®istry::class(py, RecordId::FutureCursorSafetyEvidence)?, + let safety = new_record_with( + py, + RecordId::FutureCursorSafetyEvidence, &[ ("action", native_field(&config, "action")?), ("max_distance", distance), ( "invocation_time", call_with( - ®istry::class(py, RecordId::TimestampValue)?, + &record_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 b376d39a61..9c1863ff17 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 @@ -13,6 +13,7 @@ use crate::bindings::_helpers::planner_resolution::cursor_safety::{ horizon, replace, safety_duration, safety_error, timestamp_bound, utc_clock, }; use crate::bindings::_helpers::planner_resolution::values::string; +use crate::bindings::_helpers::records::construct::{new_record_with, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -184,15 +185,16 @@ pub(crate) fn apply_maximum_start_policy<'py>( }; let relation = native_field(&snapshot, "target_relation")?; let column = native_field(&snapshot, "destination_cursor_column")?; - let evidence = call_with( - ®istry::class(py, RecordId::MaximumStartSafetyEvidence)?, + let evidence = new_record_with( + py, + RecordId::MaximumStartSafetyEvidence, &[ ("action", native_field(&config, "action")?), ("max_ahead", native_field(&config, "max_ahead")?), ( "invocation_time", call_with( - ®istry::class(py, RecordId::TimestampValue)?, + &record_class(py, RecordId::TimestampValue)?, &[("value", utc_clock(&clock)?)], )?, ), 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 ab2abd45d0..7afaa0d38e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/orchestration.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/orchestration.rs @@ -18,6 +18,7 @@ use crate::bindings::_helpers::planner_resolution::refs::{ resolve_udf_references, }; use crate::bindings::_helpers::planner_resolution::sources::resolve_sources; +use crate::bindings::_helpers::records::construct::record_class; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -106,7 +107,7 @@ impl<'py> Resolution<'py> { fn hook(&self, value: Bound<'py, PyAny>, label: &str) -> PyResult> { let py = value.py(); - if value.is_instance(®istry::class(py, RecordId::SqlHookEntry)?)? { + if value.is_instance(&record_class(py, RecordId::SqlHookEntry)?)? { let kwargs = PyDict::new(py); kwargs.set_item( "statement", 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 649f4bd0aa..a3ba61f1fe 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/artifacts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/artifacts.rs @@ -1,14 +1,14 @@ //! Scenario artifact dataclasses and adapter identifier callbacks. -use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; 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::{error, string}; +use crate::bindings::_helpers::records::construct::{enum_from_value, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; 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}; @@ -152,8 +152,7 @@ pub(crate) fn build_scenario_artifact_name( identifier_limit: i64, ) -> PyResult { let py = kind.py(); - let kind: String = registry::class(py, RecordId::ScenarioArtifactKind)? - .call1((kind,))? + let kind: String = enum_from_value(py, RecordId::ScenarioArtifactKind, kind)? .getattr("value")? .extract()?; build_scenario_artifact_physical_name(hash_prefix, &kind, logical_name, identifier_limit) @@ -167,8 +166,9 @@ pub(crate) fn parse_scenario_artifact_physical_name<'py>( let Some((hash, kind, logical)) = parse_artifact_name::parse_artifact_name(name) else { return Ok(py.None().into_bound(py)); }; - call_with( - ®istry::class(py, RecordId::ParsedScenarioArtifactName)?, + new_record_with( + py, + RecordId::ParsedScenarioArtifactName, &[ ("hash_prefix", string(py, hash)), ("kind", string(py, kind)), @@ -222,15 +222,20 @@ pub(crate) fn build_scenario_relation_map<'py>( normalize.call1((&physical,))? }; if let Some(existing) = seen.get_item(&normalized)? { - let kind = registry::class(py, RecordId::ScenarioArtifactKind)?; - let old_kind: String = kind - .call1((native_field(&existing, "kind")?,))? - .getattr("value")? - .extract()?; - let new_kind: String = kind - .call1((native_field(&artifact, "kind")?,))? - .getattr("value")? - .extract()?; + let old_kind: String = enum_from_value( + py, + RecordId::ScenarioArtifactKind, + native_field(&existing, "kind")?, + )? + .getattr("value")? + .extract()?; + let new_kind: String = enum_from_value( + py, + RecordId::ScenarioArtifactKind, + native_field(&artifact, "kind")?, + )? + .getattr("value")? + .extract()?; let old_name: String = native_field(&existing, "logical_name")?.extract()?; let new_name: String = native_field(&artifact, "logical_name")?.extract()?; return Err(error( @@ -243,16 +248,18 @@ pub(crate) fn build_scenario_relation_map<'py>( )?); } seen.set_item(normalized, &artifact)?; - artifacts.push(call_with( - ®istry::class(py, RecordId::ScenarioArtifactName)?, + artifacts.push(new_record_with( + py, + RecordId::ScenarioArtifactName, &[ ("identity", artifact), ("physical_name", string(py, &physical)), ], )?); } - call_with( - ®istry::class(py, RecordId::ScenarioRelationMap)?, + new_record_with( + 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 d41ffba177..d84016d221 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/cli.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/cli.rs @@ -12,6 +12,7 @@ use crate::bindings::_helpers::planner_scenario::graph::plan_scenario_graph; use crate::bindings::_helpers::planner_scenario::models::ScenarioRelations; use crate::bindings::_helpers::planner_scenario::relations::build_scenario_relation_plan; use crate::bindings::_helpers::planner_scenario::values::string; +use crate::bindings::_helpers::records::construct::{enum_member, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -70,12 +71,13 @@ pub(crate) fn build_cli_scenario_plan<'py>( ("model_names", "MODEL"), ] { for name in graph.getattr(field)?.try_iter()? { - identities.push(call_with( - ®istry::class(py, RecordId::ScenarioArtifactIdentity)?, + identities.push(new_record_with( + py, + RecordId::ScenarioArtifactIdentity, &[ ( "kind", - registry::class(py, RecordId::ScenarioArtifactKind)?.getattr(kind)?, + enum_member(py, RecordId::ScenarioArtifactKind, kind)?, ), ("logical_name", name?), ], @@ -156,7 +158,7 @@ fn namespace<'py>( fn raise_for_warnings(warnings: &Bound<'_, PyAny>) -> PyResult<()> { let py = warnings.py(); - let severity = registry::class(py, RecordId::WarningSeverity)?.getattr("ERROR")?; + let severity = enum_member(py, RecordId::WarningSeverity, "ERROR")?; let mut messages: Vec = Vec::new(); let mut code = None; for warning in warnings.try_iter()? { 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 f2d801ae36..6e51ce0400 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/entries.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/entries.rs @@ -1,7 +1,7 @@ //! Existing seed and function plan dataclass construction. +use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; use crate::bindings::_helpers::compile_driver::python_calls::keywords; -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_identity::metadata::build_compiled_function_fingerprint_sql; use crate::bindings::_helpers::planner_resolution::orchestration::resolve_function_sql; @@ -10,9 +10,9 @@ use crate::bindings::_helpers::planner_scenario::{ models::SqlContext, sql::required_target, values::error, }; use crate::bindings::_helpers::planner_strategy::model_entries::extract_seed_columns; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; 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::PyString; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; @@ -47,8 +47,9 @@ pub(super) fn seed_entries<'py>( &name.extract::()?, "seed", )?; - result.push(call_with( - ®istry::class(py, RecordId::SeedPlanEntry)?, + result.push(new_record_with( + py, + RecordId::SeedPlanEntry, &[ ("key", native_field(&seed, "key")?), ("name", native_field(&seed, "name")?), @@ -130,7 +131,7 @@ pub(super) fn function_entry<'py>( ] { fields.push((name, function.getattr(name)?)); } - call_with(®istry::class(py, RecordId::FunctionPlanEntry)?, &fields) + new_record_with(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 f7a0043f37..4c04c67a14 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/execution.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/execution.rs @@ -2,9 +2,7 @@ use crate::bindings::_helpers::analysis::model_reads; use crate::bindings::_helpers::compile_driver::python_calls::keywords; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported}; use crate::bindings::_helpers::compiled_project::loop_classes::by_attribute; use crate::bindings::_helpers::compiled_project::records::compiled_model_record; use crate::bindings::_helpers::planner_entries::model_entries::plan_model; @@ -23,9 +21,9 @@ use crate::bindings::_helpers::planner_scenario::{ }; use crate::bindings::_helpers::planner_strategy::fixture_completion::complete_fixtures as complete_relation_fixtures; use crate::bindings::_helpers::planner_strategy::fixture_context::planning_context; +use crate::bindings::_helpers::records::construct::{enum_member, new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; 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::{PyBool, PyDict, PyDictMethods, PyFrozenSet, PyTuple}; @@ -68,7 +66,7 @@ pub(crate) fn build_scenario_execution_plan<'py>( let snapshot = if snapshot.is_truthy()? { snapshot } else { - registry::class(py, RecordId::WarehouseSnapshot)?.call0()? + new_record(py, RecordId::WarehouseSnapshot, (), None)? }; let columns = args.value("source_warehouse_columns")?; let columns = if columns.is_truthy()? { @@ -158,7 +156,7 @@ pub(crate) fn build_scenario_execution_plan<'py>( ("full_refresh", PyBool::new(py, true).to_owned().into_any()), ( "cursor_overrides", - registry::class(py, RecordId::CursorOverridePair)?.call0()?, + new_record(py, RecordId::CursorOverridePair, (), None)?, ), ], )?), @@ -170,8 +168,9 @@ pub(crate) fn build_scenario_execution_plan<'py>( } } let (expected, assertions) = expectations(&scenario, &context)?; - let plan = call_with( - ®istry::class(py, RecordId::ScenarioExecutionPlan)?, + let plan = new_record_with( + py, + RecordId::ScenarioExecutionPlan, &[ ("key", native_field(&scenario, "key")?), ("name", native_field(&scenario, "name")?), @@ -205,7 +204,7 @@ fn complete_fixtures<'py>( ("MODEL", "__ref__"), ("SEED", "__seed__"), ] { - let kind = registry::class(py, RecordId::CompiledResourceType)?.getattr(kind)?; + let kind = enum_member(py, RecordId::CompiledResourceType, kind)?; groups.push(PyTuple::new( py, [kind, extract_fixtures(scenario, prefix)?.into_any()], @@ -297,8 +296,9 @@ fn expectations<'py>( &native_field(&cte, "sql_body")?.extract::()?, false, )?; - expected.push(call_with( - ®istry::class(py, RecordId::ScenarioExpectedExpectationPlan)?, + expected.push(new_record_with( + py, + RecordId::ScenarioExpectedExpectationPlan, &[ ("model_name", string(py, name)), ("actual_destination", target), @@ -315,8 +315,9 @@ fn expectations<'py>( &native_field(&cte, "sql_body")?.extract::()?, false, )?; - assertions.push(call_with( - ®istry::class(py, RecordId::ScenarioAssertionExpectationPlan)?, + assertions.push(new_record_with( + 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 dc88417c35..38eb8b00db 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/fixtures.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/fixtures.rs @@ -1,15 +1,15 @@ //! Materialize fixture SQL plans while preserving adapter and completion boundaries. -use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; 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::{error, string}, }; +use crate::bindings::_helpers::records::construct::{enum_from_value, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; 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,12 +90,13 @@ pub(super) fn fixture_plans<'py>( } else { format!("WITH {} {sql}", helpers.join(", ")) }; - result.push(call_with( - ®istry::class(py, RecordId::ScenarioFixturePlan)?, + result.push(new_record_with( + py, + RecordId::ScenarioFixturePlan, &[ ( "kind", - registry::class(py, RecordId::ScenarioArtifactKind)?.call1((kind,))?, + enum_from_value(py, RecordId::ScenarioArtifactKind, 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 dc1dd028e4..f3be3d84ae 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/graph.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/graph.rs @@ -1,13 +1,12 @@ //! Convert project views into pure scenario dependency facts. use crate::bindings::_helpers::analysis::model_reads; -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::construct::{enum_member, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; 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; @@ -60,8 +59,9 @@ pub(crate) fn plan_scenario_graph<'py>( let output = graph(&input); input.targets.sort(); input.assertions.sort(); - let plan = call_with( - ®istry::class(py, RecordId::ScenarioGraphPlan)?, + let plan = new_record_with( + py, + RecordId::ScenarioGraphPlan, &[ ("key", native_field(scenario, "key")?), ("name", native_field(scenario, "name")?), @@ -103,17 +103,14 @@ pub(crate) fn plan_scenario_graph<'py>( ("model_name", py.None().into_bound(py)), ( "severity", - registry::class(py, RecordId::WarningSeverity)?.getattr("ERROR")?, + enum_member(py, RecordId::WarningSeverity, "ERROR")?, ), ("message", string(py, &message)), ]; if coded { values.push(("code", string(py, SCENARIO_PLAN_GRAPH_VALIDATION))); } - warnings.push(call_with( - ®istry::class(py, RecordId::PlanWarning)?, - &values, - )?); + warnings.push(new_record_with(py, RecordId::PlanWarning, &values)?); } Ok((plan, PyTuple::new(py, warnings)?)) } 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 e701a6d8ea..e18fe48ad9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/relations.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/relations.rs @@ -9,9 +9,9 @@ use crate::bindings::_helpers::planner_scenario::constants::{ }; use crate::bindings::_helpers::planner_scenario::models::ScenarioRelations; use crate::bindings::_helpers::planner_scenario::values::error; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; 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}; @@ -120,8 +120,9 @@ pub(crate) fn build_scenario_relation_plan<'py>( }; seed_fixtures.set_item(name, target)?; } - call_with( - ®istry::class(py, RecordId::ScenarioRelationPlan)?, + new_record_with( + py, + RecordId::ScenarioRelationPlan, &[ ("scenario_name", native_field(graph, "name")?), ("relation_map", args.relation_map.clone()), @@ -162,8 +163,9 @@ fn target_for_artifact<'py>( ("name", physical.clone()), ], )?; - call_with( - ®istry::class(py, RecordId::CompiledRelationLocation)?, + new_record_with( + py, + RecordId::CompiledRelationLocation, &[ ("database", database), ("schema", schema), 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 41cc14bbb3..d803377b22 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/loaders.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/loaders.rs @@ -6,9 +6,9 @@ use crate::bindings::_helpers::graph::views::edge_dict; use crate::bindings::_helpers::planner_scope::values::{ dependency_names, edges, keys, loader_graph, loader_names, project_graph, }; +use crate::bindings::_helpers::records::construct::new_record; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::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::{ @@ -68,7 +68,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)?; - registry::class(py, RecordId::SourceEntry)?.call((), Some(&kwargs)) + new_record(py, RecordId::SourceEntry, (), 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 72ac0202d4..c5452eb291 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/scope.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/scope.rs @@ -1,6 +1,6 @@ //! Planner scope assembly using native graph and selector operations. -use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; use crate::bindings::_helpers::graph::keys::KeyCache; use crate::bindings::_helpers::graph::project_graph::NativeProjectGraph; use crate::bindings::_helpers::graph::views::lineage_graph_names; @@ -12,9 +12,9 @@ use crate::bindings::_helpers::planner_scope::loaders::expand_selected_loader_de use crate::bindings::_helpers::planner_scope::values::{edges, keys}; use crate::bindings::_helpers::planner_selection::carrier::{Carrier, py_strings}; use crate::bindings::_helpers::planner_selection::selectors::resolve_graph_selection; +use crate::bindings::_helpers::records::construct::{new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; 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::PyDictMethods; use pyo3::types::{PyDict, PyFrozenSet, PyTuple}; @@ -132,10 +132,11 @@ pub(crate) fn build_planner_scope<'py>( } let mut tests = args.value("sql_test_selection")?; if !tests.is_truthy()? { - tests = registry::class(py, RecordId::SqlTestSelection)?.call0()?; + tests = new_record(py, RecordId::SqlTestSelection, (), None)?; } - call_with( - ®istry::class(py, RecordId::PlannerScope)?, + new_record_with( + 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 eb19c0135a..9a555521d0 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/sources.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/sources.rs @@ -4,9 +4,9 @@ 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::records::construct::new_record; use crate::bindings::_helpers::records::fields::field as native_field; 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}; @@ -56,7 +56,6 @@ pub(crate) fn build_source_load_entries<'py>( is_reload: bool, ) -> PyResult> { let py = execution_order.py(); - 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()? { @@ -113,7 +112,12 @@ pub(crate) fn build_source_load_entries<'py>( native_field(&integration, "kind")? }, )?; - entries.push(class.call((), Some(&kwargs))?); + entries.push(new_record( + py, + RecordId::SourceLoadPlanEntry, + (), + Some(&kwargs), + )?); } PyTuple::new(py, entries) } 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 c4d14e7408..02a8754ce5 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 @@ -12,11 +12,11 @@ use sqlbuild_analysis::planner_selection::models::{ ModelSelectorNames, SelectableModel, SelectorExpansion, }; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::planner_selection::carrier::Carrier; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; const SHARED_EXCEPTIONS_MODULE: &str = "sqlbuild.errors.contracts.exceptions"; @@ -36,8 +36,9 @@ fn split_selector_expansion<'py>(raw: &Bound<'py, PyString>) -> PyResult = Carrier::single(raw)?; let expansion: SelectorExpansion = split(&carrier.send(raw)?).map_err(|message| shared_input_error(&carrier, &message))?; - call_with( - ®istry::class(py, RecordId::SelectorExpansion)?, + new_record_with( + py, + RecordId::SelectorExpansion, &[ ("core", carrier.back(&expansion.core)?.into_any()), ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/selectors.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/selectors.rs index 2a77feee21..0be914cf65 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/selectors.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/selectors.rs @@ -14,15 +14,15 @@ use sqlbuild_analysis::graph::main::resolve_selectors::resolve_selectors; use sqlbuild_analysis::graph::models::{ParsedSelector, ProjectGraph}; use sqlbuild_analysis::planner_selection::main::unit_test_selector_rejection::unit_test_selector_rejection; -use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; use crate::bindings::_helpers::graph::algorithms::closure; use crate::bindings::_helpers::graph::keys::KeyCache; use crate::bindings::_helpers::graph::project_graph::{CompiledProjectGraph, NativeProjectGraph}; use crate::bindings::_helpers::graph::views::{GraphViewMappings, graph_from_view_mappings}; use crate::bindings::_helpers::planner_selection::carrier::{Carrier, py_strings}; +use crate::bindings::_helpers::records::construct::{enum_from_value, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; const VIEW_OPTIONS: &[&str] = &[ "all_keys", @@ -44,13 +44,11 @@ pub(crate) fn parsed_selector<'py>( value, upstream, downstream, - } => call_with( - ®istry::class(py, RecordId::ParsedSelector)?, + } => new_record_with( + py, + RecordId::ParsedSelector, &[ - ( - "kind", - registry::class(py, RecordId::SelectorKind)?.call1((kind,))?, - ), + ("kind", enum_from_value(py, RecordId::SelectorKind, kind)?), ("value", carrier.back(&value)?.into_any()), ("upstream", PyBool::new(py, upstream).to_owned().into_any()), ( @@ -64,8 +62,9 @@ pub(crate) fn parsed_selector<'py>( end, upstream, downstream, - } => call_with( - ®istry::class(py, RecordId::PathSelector)?, + } => new_record_with( + 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 b7cb0c1ccf..6ac509d8a7 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,11 +10,10 @@ use sqlbuild_analysis::planner_selection::models::{ SelectorGroups, UnitTestSelectorNames, UnitTestSelectorSplit, }; -use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::planner_selection::carrier::{Carrier, py_strings}; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; 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,8 +110,9 @@ fn split_python<'py>( accepts_unit_tests, ) .map_err(|error| carrier.error(error))?; - let selection: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::SqlTestSelection)?, + let selection: Bound<'py, PyAny> = new_record_with( + py, + RecordId::SqlTestSelection, &[ ("names", PyFrozenSet::new(py, &split.test_names)?.into_any()), ( @@ -129,8 +129,9 @@ fn split_python<'py>( ), ], )?; - call_with( - ®istry::class(py, RecordId::UnitTestSelectorSplit)?, + new_record_with( + 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 23ca89d791..f6e08bae68 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 @@ -8,6 +8,7 @@ use crate::bindings::_helpers::planner_phases::objects::object; use crate::bindings::_helpers::planner_strategy::audits::{ reads_outside_target_lineage, resolve_attachment_kind, resolve_effective_run_scope, }; +use crate::bindings::_helpers::records::construct::{enum_from_value, new_record, new_record_with}; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::pyfunction; use pyo3::types::{ @@ -15,7 +16,7 @@ use pyo3::types::{ }; 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::imported; use crate::bindings::_helpers::planner_strategy::constants::{ AUDIT_TYPES_MODULE, COMPILE_TYPES_MODULE, MODEL_ATTACHMENT, SOURCE_ATTACHMENT, @@ -23,7 +24,6 @@ use crate::bindings::_helpers::planner_strategy::constants::{ use crate::bindings::_helpers::planner_strategy::models::AuditPlanning; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; /// Render and validate one audit query with the supplied adapter callbacks. fn render_query<'py>( @@ -185,7 +185,7 @@ fn plan_audit<'py>( ] { values.set_item(field, value)?; } - registry::class(py, RecordId::AuditPlanEntry)?.call((), Some(&values)) + new_record(py, RecordId::AuditPlanEntry, (), Some(&values)) } /// Select attached audits by their logical target and singular audits by dependency overlap. @@ -202,10 +202,10 @@ pub(crate) fn audit_is_selected( let target_value = imported(py, COMPILE_TYPES_MODULE, "AttachedAuditTargetKind")? .call1((kind,))? .getattr("value")?; - let resource_type = - registry::class(py, RecordId::CompiledResourceType)?.call1((target_value,))?; - let key = call_with( - ®istry::class(py, RecordId::CompiledObjectKey)?, + let resource_type = enum_from_value(py, RecordId::CompiledResourceType, target_value)?; + let key = new_record_with( + py, + RecordId::CompiledObjectKey, &[("resource_type", resource_type), ("name", name)], )?; return selected_keys.contains(key); 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 e08ffec28f..b862f48152 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/audits.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/audits.rs @@ -9,12 +9,12 @@ use sqlbuild_analysis::planner_strategy::main::effective_run_scope::effective_ru use sqlbuild_analysis::planner_strategy::main::singular_attachment::singular_attachment; use sqlbuild_analysis::planner_strategy::models::{AuditAttachmentKind, AuditRunScope}; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::graph::algorithms::closure; use crate::bindings::_helpers::planner_strategy::values::{input_error, optional_string, text}; +use crate::bindings::_helpers::records::construct::{enum_from_value, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; 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_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; @@ -23,12 +23,13 @@ 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( - ®istry::class(py, RecordId::CompiledObjectKey)?, + new_record_with( + py, + RecordId::CompiledObjectKey, &[ ( "resource_type", - registry::class(py, RecordId::CompiledResourceType)?.call1((kind,))?, + enum_from_value(py, RecordId::CompiledResourceType, kind)?, ), ("name", PyString::new(py, name).into_any()), ], 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 1b589fff66..dcd33c1d73 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 @@ -10,7 +10,6 @@ use sqlbuild_analysis::planner_strategy::models::{CursorInputColumnCheck, Cursor use crate::bindings::_helpers::analysis::model_reads; 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; 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; @@ -21,9 +20,9 @@ use crate::bindings::_helpers::planner_strategy::constants::{ use crate::bindings::_helpers::planner_strategy::lineage::resolve_lineage_reference; use crate::bindings::_helpers::planner_strategy::models::CursorInputLookups; use crate::bindings::_helpers::planner_strategy::values::{input_error, text}; +use crate::bindings::_helpers::records::construct::{enum_from_value, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; 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. @@ -207,7 +206,7 @@ fn input_kind<'py>( SOURCE_REFERENCE => "source", _ => return Ok(py.None().into_bound(py)), }; - registry::class(py, RecordId::GraphResourceKind)?.call1((value,)) + enum_from_value(py, RecordId::GraphResourceKind, value) } /// A producer model's terminal cursor bound parsed with the consumer's cursor type. @@ -268,7 +267,6 @@ 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> = registry::class(py, RecordId::CursorInputRelation)?; let mut relations: Vec> = Vec::new(); let watermarks: Bound<'py, PyAny> = native_field(&resolve_cursor_input_roles(&model)?, "watermark_inputs")?; @@ -297,8 +295,9 @@ pub(crate) fn build_cursor_input_relations<'py>( None => py.None().into_bound(py), }; let model_backed: bool = kind == MODEL_REFERENCE && model_locations.contains(&name)?; - relations.push(call_with( - &class, + relations.push(new_record_with( + py, + RecordId::CursorInputRelation, &[ ("relation", PyString::new(py, &relation).into_any()), ("cursor_column", column), 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 c13379eff9..062b6a02bd 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,8 +1,8 @@ //! Fixture boundary records and adapter callbacks. +use crate::bindings::_helpers::records::construct::new_record; use crate::bindings::_helpers::records::fields::field as native_field; 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}; use sqlbuild_analysis::planner_fixtures::models::FixtureColumn; @@ -36,7 +36,7 @@ pub(crate) fn record<'py>( name: RecordId, kwargs: &Bound<'py, PyDict>, ) -> PyResult> { - registry::class(py, name)?.call((), Some(kwargs)) + new_record(py, name, (), Some(kwargs)) } pub(crate) fn label(key: &Bound<'_, PyAny>) -> PyResult { 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 26f52ceb9b..0ae7efdcdf 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 @@ -1,6 +1,7 @@ //! Microbatch limits applied to planned model entries in CLI, project and model precedence. use crate::bindings::_helpers::planner_changes::results::native_enum; +use crate::bindings::_helpers::records::construct::{enum_member, new_record_with, record_class}; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python}; use pyo3::types::{PyString, PyTuple}; use pyo3::{IntoPyObject, pyfunction}; @@ -11,7 +12,6 @@ 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; 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::microbatches::{ @@ -20,7 +20,6 @@ use crate::bindings::_helpers::planner_strategy::microbatches::{ use crate::bindings::_helpers::planner_strategy::values::{input_error, optional_string, text}; use crate::bindings::_helpers::records::fields::field as native_field; 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 { - registry::class(py, RecordId::CursorGrain) + record_class(py, RecordId::CursorGrain) .and_then(|class| class.call1((error.0,))) .err() .unwrap_or_else(|| pyo3::exceptions::PyValueError::new_err("unknown cursor grain")) @@ -151,8 +150,9 @@ fn limited<'py>( if action_text == ERROR_ACTION { return Err(input_error(py, ErrorId::CompileInput, &message, None)); } - let plan_warning: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::PlanWarning)?, + let plan_warning: Bound<'py, PyAny> = new_record_with( + py, + RecordId::PlanWarning, &[ ("model_name", name), ( @@ -235,8 +235,7 @@ pub(crate) fn apply_configured_microbatch_limits<'py>( .getattr("max_microbatches_is_override")? .is_truthy()? { - let error: Bound<'py, PyAny> = - registry::class(py, RecordId::MicrobatchLimitAction)?.getattr("ERROR")?; + let error: Bound<'py, PyAny> = enum_member(py, RecordId::MicrobatchLimitAction, "ERROR")?; return with_warnings(limited(entry, configured, error)?, Vec::new()); } let declared: Bound<'py, PyAny> = native_field(entry, "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 52164499b0..6f5a1455ed 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/microbatches.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/microbatches.rs @@ -18,13 +18,14 @@ 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; use crate::bindings::_helpers::cursor::durations::shifted_moment; use crate::bindings::_helpers::plan_output::display::aligned_timestamp_bounds; use crate::bindings::_helpers::planner_strategy::values::text; +use crate::bindings::_helpers::records::construct::{ + enum_from_value, new_record_with, record_class, +}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; const INTEGER_CURSOR: &str = "integer"; const TIMESTAMP_CURSOR: &str = "timestamp"; @@ -87,7 +88,7 @@ fn typed_bounds<'py>( for value in [&start, &end] { let mut typed: bool = false; for class in classes { - typed = typed || value.is_instance(®istry::class(py, *class)?)?; + typed = typed || value.is_instance(&record_class(py, *class)?)?; } if !typed { return Ok(None); @@ -157,8 +158,9 @@ fn cursor_bounds<'py>( end: Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = start.py(); - call_with( - ®istry::class(py, RecordId::CursorBounds)?, + new_record_with( + py, + RecordId::CursorBounds, &[("start", start), ("end", end)], ) } @@ -169,7 +171,7 @@ fn cursor_value<'py>( class: RecordId, value: Bound<'py, PyAny>, ) -> PyResult> { - call_with(®istry::class(py, class)?, &[("value", value)]) + new_record_with(py, class, &[("value", value)]) } /// One concrete range capped to `max_batches` batch windows from the end the action keeps. @@ -273,7 +275,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> = - registry::class(py, RecordId::MicrobatchLimitAction)?.call1((action,))?; + enum_from_value(py, RecordId::MicrobatchLimitAction, 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 1166105cf1..9dbe65f459 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 @@ -32,9 +32,9 @@ use crate::bindings::_helpers::planner_strategy::strategy::{ model_warning_records, resolve_schema_actions, warning_inputs, }; use crate::bindings::_helpers::planner_strategy::values::{input_error, optional_string, text}; +use crate::bindings::_helpers::records::construct::{enum_from_value, new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, 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] = [ @@ -146,7 +146,7 @@ fn on_schema_change<'py>(model: &Bound<'py, PyAny>) -> PyResult() { - match registry::class(py, RecordId::OnSchemaChange)?.call1((&raw,)) { + match enum_from_value(py, RecordId::OnSchemaChange, &raw) { Ok(policy) => return Ok(policy), Err(error) if !error.is_instance_of::(py) => return Err(error), Err(_) => {} @@ -355,8 +355,9 @@ fn ddl_cursor_bounds<'py>(request: &ModelEntryDecisions<'py>) -> PyResult = host::entry(py, ExecutorHost::BoundSentinel)?; - call_with( - ®istry::class(py, RecordId::CursorBounds)?, + new_record_with( + py, + RecordId::CursorBounds, &[ ("start", native_field(&sentinel, "START")?), ("end", native_field(&sentinel, "END")?), @@ -569,7 +570,6 @@ pub(crate) fn assemble_model_plan_entry<'py>( text(&request.materialization_type)? == CUSTOM_MATERIALIZATION, &entry, )?; - let plan_entry: Bound<'py, PyAny> = - registry::class(py, RecordId::ModelPlanEntry)?.call((), Some(&entry))?; + let plan_entry: Bound<'py, PyAny> = new_record(py, RecordId::ModelPlanEntry, (), Some(&entry))?; Ok((plan_entry, warnings)) } 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 f3057d8e3a..da08459f8e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/strategy.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/strategy.rs @@ -2,6 +2,7 @@ use crate::bindings::_helpers::analysis::model_reads; use crate::bindings::_helpers::planner_changes::results::native_enum; +use crate::bindings::_helpers::records::construct::new_record_with; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyBool, PyDict, PyString, PyTuple}; use pyo3::{IntoPyObject, pyfunction, wrap_pyfunction}; @@ -14,14 +15,13 @@ use sqlbuild_analysis::planner_strategy::models::{ ModelStrategy, PlanAction, PlanReason, SchemaActionKind, WarningInputs, }; -use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; use crate::bindings::_helpers::planner_changes::results::planner_enum; use crate::bindings::_helpers::planner_strategy::values::{ 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 { @@ -114,7 +114,6 @@ pub(crate) fn resolve_schema_actions<'py>( on_schema_change: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = schema_findings.py(); - 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()) { @@ -123,8 +122,9 @@ pub(crate) fn resolve_schema_actions<'py>( SchemaActionKind::DropColumn => "drop_column", SchemaActionKind::AlterColumnType => "alter_column_type", }; - actions.push(call_with( - &class, + actions.push(new_record_with( + py, + RecordId::SchemaAction, &[ ("kind", native_enum(py, RecordId::SchemaActionKind, kind)?), ( 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 d899781756..e268df43c0 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/values.rs @@ -1,6 +1,7 @@ //! Conversions between the planner's Python models and the native strategy rows. use crate::bindings::_helpers::planner_changes::results::native_enum; +use crate::bindings::_helpers::records::construct::new_record_with; use pyo3::IntoPyObject; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyString}; @@ -13,7 +14,6 @@ use sqlbuild_analysis::planner_strategy::models::{ ChangeFacts, MaterializationType, PlanChangeKind, WarningRow, WarningSeverity, }; -use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -137,8 +137,9 @@ pub(crate) fn plan_warning<'py>( WarningSeverity::Warning => "warning", WarningSeverity::Info => "info", }; - call_with( - ®istry::class(py, RecordId::PlanWarning)?, + new_record_with( + py, + RecordId::PlanWarning, &[ ("model_name", model_name.clone()), ( 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 6aaaacff26..64d3938452 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/cursors.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/cursors.rs @@ -28,9 +28,9 @@ 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::construct::record_class; use crate::bindings::_helpers::records::fields::field as native_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 pyo3::IntoPyObject; use pyo3::exceptions::PyException; @@ -870,7 +870,7 @@ fn eligible_target_maxes<'py>( ( "is_date", horizon - .is_instance(®istry::class(py, DATE_VALUE)?)? + .is_instance(&record_class(py, DATE_VALUE)?)? .into_pyobject(py)? .to_owned() .into_any(), 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 4321c6ccdc..cbd81e0167 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 @@ -13,8 +13,8 @@ use crate::bindings::_helpers::planner_phases::objects::{ use crate::bindings::_helpers::planner_warehouse::constants::{ MODEL_HOOK_CONFIG_KEYS, SQL_HOOK_ENTRY, }; +use crate::bindings::_helpers::records::construct::record_class; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::pyfunction; use pyo3::types::PyDict as KeywordDict; @@ -50,7 +50,7 @@ fn hook_source_reads( ) -> PyResult> { let mut names: Vec = Vec::new(); let py = model.py(); - let hook_entry = registry::class(py, SQL_HOOK_ENTRY)?; + let hook_entry = record_class(py, SQL_HOOK_ENTRY)?; let source_kind = host::entry(py, UserCodeHost::SqlResourceRefKind)?.getattr("SOURCE")?; let values = native_field(model, "config")?.getattr("values")?; for key in MODEL_HOOK_CONFIG_KEYS { 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 3ebd4939b5..1da8bab148 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 @@ -12,9 +12,9 @@ use crate::bindings::_helpers::planner_phases::objects::{ }; use crate::bindings::_helpers::planner_warehouse::constants::RUN_SCOPE; use crate::bindings::_helpers::planner_warehouse::snapshot::{destination_nodes, lower}; +use crate::bindings::_helpers::records::construct::enum_member; use crate::bindings::_helpers::records::fields::field as native_field; 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}; @@ -57,7 +57,7 @@ pub(crate) fn source_freshness_unknown_warnings<'py>( ("model_name", py.None().into_bound(py)), ( "severity", - registry::class(py, RecordId::WarningSeverity)?.getattr("WARNING")?, + enum_member(py, RecordId::WarningSeverity, "WARNING")?, ), ("message", text(py, &message)), ], From 11671f978b28839ef343f0ce7a12b2ff68aad7e7 Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Sat, 17 Oct 2026 02:18:26 +0100 Subject: [PATCH 04/28] =?UTF-8?q?refactor:=20construct=20native=20records?= =?UTF-8?q?=20directly=20in=20record=5Fbehaviours=20=E2=80=A6=20rules=20bi?= =?UTF-8?q?ndings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The host payload facts test now asserts the observed dropped fields instead of spying on the CompiledModel constructor and dataclasses.replace seams. --- .../_helpers/record_behaviours/compiled.rs | 28 ++++++---- .../_helpers/record_behaviours/cursors.rs | 8 +-- .../_helpers/record_behaviours/freshness.rs | 6 +-- .../record_behaviours/lineage_facts.rs | 16 +++--- .../_helpers/record_behaviours/migrations.rs | 6 +-- .../_helpers/record_behaviours/planner.rs | 13 ++--- .../_helpers/record_behaviours/rule_shapes.rs | 11 ++-- .../_helpers/refactor/contract_compare.rs | 11 ++-- .../_helpers/refactor/contract_edits.rs | 5 +- .../_helpers/refactor/offline_compile.rs | 30 +++++------ .../_helpers/refactor/python_strings.rs | 10 ++-- .../_helpers/refactor/refactor_facts.rs | 8 ++- .../bindings/_helpers/render/authored_sql.rs | 24 ++++----- .../_helpers/render/declaration_values.rs | 52 +++++++++---------- .../_helpers/render/macro_expansion_result.rs | 37 ++++++------- .../_helpers/rules/builtin_catalogue.rs | 12 +++-- .../_helpers/rules/custom_evaluation.rs | 10 ++-- .../bindings/_helpers/rules/custom_hosts.rs | 4 +- .../bindings/_helpers/rules/dsl_headers.rs | 9 ++-- .../bindings/_helpers/rules/fact_replay.rs | 4 +- .../src/bindings/_helpers/rules/findings.rs | 3 +- .../src/bindings/_helpers/rules/fix_facts.rs | 8 +-- .../bindings/_helpers/rules/fix_session.rs | 15 +++--- .../bindings/_helpers/rules/fix_verdict.rs | 8 +-- .../bindings/_helpers/rules/lint_headers.rs | 12 ++--- .../bindings/_helpers/rules/lint_pipeline.rs | 12 +++-- .../_helpers/rules/lint_preparation.rs | 11 ++-- .../src/bindings/_helpers/rules/lint_text.rs | 20 +++---- .../src/bindings/_helpers/rules/rows.rs | 6 ++- .../src/bindings/_helpers/rules/rule_api.rs | 4 +- .../bindings/_helpers/rules/rule_dispatch.rs | 6 ++- .../src/bindings/_helpers/rules/rule_plan.rs | 6 ++- .../src/bindings/_helpers/rules/rule_run.rs | 14 ++--- .../bindings/_helpers/rules/rules_config.rs | 3 +- .../src/bindings/_helpers/rules/sql_rules.rs | 14 ++--- .../_helpers/rules/sql_test_objects.rs | 4 +- .../rule_engine/host_payload/_test_types.py | 3 +- .../rule_engine/host_payload/helpers.py | 30 ----------- .../host_payload/test_host_payload_facts.py | 42 +++++++++++---- 39 files changed, 271 insertions(+), 254 deletions(-) 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 5597535659..614ffa5c93 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/compiled.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/compiled.rs @@ -4,6 +4,7 @@ use pyo3::IntoPyObjectExt; use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyErr, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyList, PyListMethods, PyTuple}; +use crate::bindings::_helpers::records::construct::{enum_member, new_record}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -35,9 +36,7 @@ const TEST_PARENT_NAME_FIELD: usize = 12; /// 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()) + Ok(new_record(py, class, (value.bind(py),), None)?.unbind()) } /// `value` coerced to `CompiledResourceType` unless it is `None`. @@ -78,7 +77,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> = - registry::class(py, RecordId::SourceLocation)?.call((), Some(&keywords))?; + new_record(py, RecordId::SourceLocation, (), Some(&keywords))?; values[DIAGNOSTIC_LOCATION_FIELD] = location.unbind(); } let location: Bound<'_, PyAny> = values[DIAGNOSTIC_LOCATION_FIELD].bind(py).clone(); @@ -93,8 +92,7 @@ pub(crate) fn finish_compiler_diagnostic( /// `CompilerDiagnostic.is_error`: whether the diagnostic fails the command. pub(crate) fn diagnostic_is_error(instance: &Bound<'_, PyAny>) -> PyResult> { let py: Python<'_> = instance.py(); - let error: Bound<'_, PyAny> = - registry::class(py, RecordId::DiagnosticSeverity)?.getattr("ERROR")?; + let error: Bound<'_, PyAny> = enum_member(py, RecordId::DiagnosticSeverity, "ERROR")?; native_field(instance, "severity")? .eq(error)? .into_py_any(py) @@ -362,8 +360,12 @@ pub(crate) fn build_context_declaration_expansion( let keywords: Bound<'_, PyDict> = PyDict::new(py); keywords.set_item( "declarations", - registry::class(py, RecordId::DeclarationResolutionContext)? - .call((), Some(&declarations))?, + new_record( + py, + RecordId::DeclarationResolutionContext, + (), + Some(&declarations), + )?, )?; keywords.set_item("value_renderer", native_field(instance, "value_renderer")?)?; keywords.set_item( @@ -371,9 +373,13 @@ pub(crate) fn build_context_declaration_expansion( native_field(instance, "collection_rendering")?, )?; keywords.set_item("resolver", native_field(instance, "declaration_resolver")?)?; - Ok(registry::class(py, RecordId::DeclarationExpansionContext)? - .call((), Some(&keywords))? - .unbind()) + Ok(new_record( + py, + RecordId::DeclarationExpansionContext, + (), + Some(&keywords), + )? + .unbind()) } /// A `CompileInputError` carrying `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 18e18bd252..03755a8b86 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/cursors.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/cursors.rs @@ -7,9 +7,9 @@ use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::cursor::scalars::parse_value; use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::construct::{enum_from_value, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; 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>; @@ -26,7 +26,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(®istry::class(py, class)?)? { + if raw.is_instance(&record_class(py, class)?)? { return Ok(raw.clone()); } } @@ -121,7 +121,7 @@ pub(crate) fn finish_cursor_snapshot( .collect::>()?; values[13] = PyTuple::new(py, pairs)?.into_any().unbind(); let mode: Bound<'_, PyAny> = - registry::class(py, RecordId::CursorWatermarkMode)?.call1((values[10].bind(py),))?; + enum_from_value(py, RecordId::CursorWatermarkMode, values[10].bind(py))?; values[10] = mode.unbind(); Ok(values) } @@ -178,7 +178,7 @@ pub(crate) fn finish_input_relation( ) -> PyResult>> { if !values[2].bind(py).is_none() { let grain: Bound<'_, PyAny> = - registry::class(py, RecordId::CursorGrain)?.call1((values[2].bind(py),))?; + enum_from_value(py, RecordId::CursorGrain, 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 0fdfe4b00e..a7a3cccbfa 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/freshness.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/freshness.rs @@ -4,8 +4,8 @@ use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::PyDict; use crate::bindings::_helpers::freshness::observation::require_aware_utc_datetime; +use crate::bindings::_helpers::records::construct::new_record; 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,5 @@ 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(registry::class(py, RecordId::SourceFreshnessIdentity)? - .call((), Some(&keywords))? - .unbind()) + Ok(new_record(py, RecordId::SourceFreshnessIdentity, (), Some(&keywords))?.unbind()) } 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 6427234029..f3c294064f 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 @@ -6,9 +6,9 @@ use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PySlice, PyTuple, PyTupleMethods}; use crate::bindings::_helpers::record_behaviours::call_arguments::call_argument; +use crate::bindings::_helpers::records::construct::{enum_member, new_record}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; /// `ColumnTransformKind` members by native transform code. const TRANSFORM_KINDS: [&str; 6] = [ @@ -41,7 +41,7 @@ fn coded_member( let Some(member) = members.get(code) else { return Err(PyIndexError::new_err("tuple index out of range")); }; - Ok(registry::member(py, class, member)?.unbind()) + Ok(enum_member(py, class, member)?.unbind()) } /// `CompactLineageFacts.resource_name(index)`: the model's name for a native relation index. @@ -80,7 +80,6 @@ fn project_row(facts: &Bound<'_, PyAny>, row: &Bound<'_, PyAny>) -> PyResult, ) = row.extract()?; - 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::()?; @@ -88,7 +87,12 @@ fn project_row(facts: &Bound<'_, PyAny>, row: &Bound<'_, PyAny>) -> PyResult = PyDict::new(py); keywords.set_item("output_column", pool.get_item(name_index)?)?; @@ -109,9 +113,7 @@ fn project_row(facts: &Bound<'_, PyAny>, row: &Bound<'_, PyAny>) -> PyResult) -> PyResult { member.str()?.extract() @@ -194,9 +194,7 @@ pub(crate) fn history_status( } else { "PENDING_ARCHIVE" }; - Ok(registry::class(py, RecordId::OldNameViewStatus)? - .getattr(status)? - .unbind()) + Ok(enum_member(py, RecordId::OldNameViewStatus, status)?.unbind()) } /// `OldNameViewHistory.old`: the relation name the compatibility view occupies. 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 f7f6d813a8..14e7bdf498 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/planner.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/planner.rs @@ -12,6 +12,9 @@ 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::construct::{ + enum_from_value, enum_member, new_record, record_class, +}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -211,7 +214,7 @@ fn override_integer<'py>( } let parsed: Bound<'py, PyAny> = parse_value(value, &PyString::new(py, INTEGER_CURSOR).into_any())?; - if parsed.is_instance(®istry::class(py, RecordId::IntegerValue)?)? { + if parsed.is_instance(&record_class(py, RecordId::IntegerValue)?)? { return Ok(parsed); } Err(invalid()) @@ -305,9 +308,9 @@ pub(crate) fn forward_only_backfill(py: Python<'_>) -> PyResult let keywords: Bound<'_, PyDict> = PyDict::new(py); keywords.set_item( "action", - registry::class(py, RecordId::BackfillAction)?.getattr("FORWARD_ONLY")?, + enum_member(py, RecordId::BackfillAction, "FORWARD_ONLY")?, )?; - registry::class(py, RecordId::BackfillResult)?.call((), Some(&keywords)) + new_record(py, RecordId::BackfillResult, (), Some(&keywords)) } /// `ScenarioArtifactIdentity.kind` coerced to `ScenarioArtifactKind`. @@ -316,9 +319,7 @@ pub(crate) fn finish_scenario_artifact_kind( mut values: Vec>, ) -> PyResult>> { if let Some(kind) = values.first_mut() { - *kind = registry::class(py, RecordId::ScenarioArtifactKind)? - .call1((kind.bind(py),))? - .unbind(); + *kind = enum_from_value(py, RecordId::ScenarioArtifactKind, kind.bind(py))?.unbind(); } Ok(values) } 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 833a295f66..feb5c65d90 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,9 +6,9 @@ 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::construct::{enum_member, new_record, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; 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"]; @@ -38,8 +38,7 @@ pub(crate) fn project_path_depth(instance: &Bound<'_, PyAny>) -> PyResult) -> PyResult> { - let project: Bound<'_, PyAny> = - registry::class(instance.py(), RecordId::RuleSubject)?.getattr("PROJECT")?; + let project: Bound<'_, PyAny> = enum_member(instance.py(), RecordId::RuleSubject, "PROJECT")?; instance .getattr("subject")? .is(&project) @@ -61,7 +60,7 @@ pub(crate) fn rule_option_class_getitem( _kwargs: Option<&Bound<'_, PyDict>>, ) -> PyResult> { let py: Python<'_> = receiver.py(); - let class: Bound<'_, PyAny> = registry::class(py, RecordId::RuleOption)?; + let class: Bound<'_, PyAny> = record_class(py, RecordId::RuleOption)?; Ok(imported(py, "types", "GenericAlias")? .call1((class, args.get_item(0)?))? .unbind()) @@ -102,9 +101,7 @@ fn rule_option( for (name, value) in extra { fields.set_item(*name, value)?; } - Ok(registry::class(py, RecordId::RuleOption)? - .call((), Some(&fields))? - .unbind()) + Ok(new_record(py, RecordId::RuleOption, (), Some(&fields))?.unbind()) } /// The builtin type `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 66e2939713..9bb465ab9c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/refactor/contract_compare.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/refactor/contract_compare.rs @@ -9,6 +9,7 @@ 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::construct::{enum_from_value, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -28,8 +29,8 @@ struct Vocabulary<'py> { impl<'py> Vocabulary<'py> { fn new(py: Python<'py>) -> PyResult { Ok(Self { - finding: registry::class(py, RecordId::ContractFinding)?, - kinds: registry::class(py, RecordId::ContractFindingKind)?, + finding: record_class(py, RecordId::ContractFinding)?, + kinds: record_class(py, RecordId::ContractFindingKind)?, replace: py.import(DATACLASSES_MODULE)?.getattr("replace")?, }) } @@ -117,7 +118,7 @@ fn column_infos<'py>( } fn resource_type<'py>(py: Python<'py>, value: &str) -> PyResult> { - registry::class(py, RecordId::CompiledResourceType)?.call1((value,)) + enum_from_value(py, RecordId::CompiledResourceType, value) } /// The evidence a model's authored contract offers, before inspection. @@ -134,7 +135,7 @@ fn model_evidence<'py>(model: &Bound<'py, PyAny>) -> PyResult> ) }; keyword_call( - ®istry::class(py, RecordId::ContractEvidence)?, + &record_class(py, RecordId::ContractEvidence)?, vec![ ("resource_type", resource_type(py, MODEL_RESOURCE)?), ("resource_name", native_field(model, "name")?), @@ -185,7 +186,7 @@ fn source_evidence<'py>( text(py, &relation) }; keyword_call( - ®istry::class(py, RecordId::ContractEvidence)?, + &record_class(py, RecordId::ContractEvidence)?, vec![ ("resource_type", resource), ("resource_name", name), 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 c541041842..e2e98015d3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/refactor/contract_edits.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/refactor/contract_edits.rs @@ -15,6 +15,7 @@ use sqlbuild_refactor::refactoring::models::{ use sqlbuild_refactor::refactoring::types::PhysicalColumn; use crate::bindings::_helpers::boundary::panics::compiler_guard; +use crate::bindings::_helpers::records::construct::{enum_member, new_record}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -172,11 +173,11 @@ fn remaining_contract_findings<'py>( keywords.set_item("resource_name", native_field(first, "resource_name")?)?; keywords.set_item( "kind", - registry::class(py, RecordId::ContractFindingKind)?.getattr("OWNERSHIP_CONFLICT")?, + enum_member(py, RecordId::ContractFindingKind, "OWNERSHIP_CONFLICT")?, )?; keywords.set_item("message", conflict)?; let ownership: Bound<'py, PyAny> = - registry::class(py, RecordId::ContractFinding)?.call((), Some(&keywords))?; + new_record(py, RecordId::ContractFinding, (), 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 93d0002d43..a17665559b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/refactor/offline_compile.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/refactor/offline_compile.rs @@ -9,8 +9,8 @@ use pyo3::types::{PyBool, PyString, PyTuple}; use crate::bindings::_helpers::compile_command::target_writer::static_sql_test_planning_diagnostics; use crate::bindings::_helpers::compile_driver::analysis::analyze_compile_project; -use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::compile_driver::python_calls::keywords; +use crate::bindings::_helpers::records::construct::{enum_member, new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -23,10 +23,6 @@ pub(crate) struct OfflineCompile<'py> { pub(crate) errors: Vec>, } -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: &[ErrorId]) -> PyResult { for class in classes { if error.value(py).is_instance(®istry::error(py, *class)?)? { @@ -49,8 +45,9 @@ fn compile_error_diagnostic<'py>(py: Python<'py>, error: &PyErr) -> PyResult(py: Python<'py>, error: &PyErr) -> PyResult( project_dir: &Path, no_cache: bool, ) -> PyResult> { - let selection: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::CompileAnalysisSelection)?, + let selection: Bound<'py, PyAny> = new_record_with( + py, + RecordId::CompileAnalysisSelection, &[("no_cache", PyBool::new(py, no_cache).to_owned().into_any())], )?; analyze_compile_project( @@ -124,12 +123,12 @@ fn analyzed<'py>( ("selected_target", py.None().into_bound(py)), ( "lineage_mode", - registry::class(py, RecordId::CompileLineageMode)?.getattr("NONE")?, + enum_member(py, RecordId::CompileLineageMode, "NONE")?, ), ("cli_vars", py.None().into_bound(py)), ( "profile_flags", - registry::class(py, RecordId::CompileProfileFlags)?.call0()?, + new_record(py, RecordId::CompileProfileFlags, (), None)?, ), ("analysis_selection", selection), ("status", py.None().into_bound(py)), @@ -191,8 +190,9 @@ pub(crate) fn compile_for_refactor<'py>( Err(error) => return Err(error), } } - let project: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::RefactorProject)?, + let project: Bound<'py, PyAny> = new_record_with( + py, + RecordId::RefactorProject, &[ ("project_dir", project_dir.into_pyobject(py)?.into_any()), ("graph", native_field(&analysis, "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 021a22be97..618efe2ca4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/refactor/python_strings.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/refactor/python_strings.rs @@ -9,9 +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::construct::new_record; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; /// The discovered Python declarations whose files a refactoring searches. const PYTHON_DECLARATIONS: [&str; 8] = [ @@ -70,7 +70,6 @@ fn find_python_string_locations<'py>( Some(context) => context.extract()?, None => None, }; - 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")?)? { @@ -91,7 +90,12 @@ fn find_python_string_locations<'py>( keywords.set_item("line", line)?; keywords.set_item("column", column)?; keywords.set_item("reason", &reason)?; - locations.push(location_type.call((), Some(&keywords))?); + locations.push(new_record( + py, + RecordId::ManualLocation, + (), + Some(&keywords), + )?); } } PyTuple::new(py, locations) 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 6c7ee9d889..9969029211 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/refactor/refactor_facts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/refactor/refactor_facts.rs @@ -11,6 +11,7 @@ use sqlbuild_refactor::refactoring::models::{ SchemaColumnFacts, SqlFileRole, }; +use crate::bindings::_helpers::records::construct::{enum_member, new_record}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -336,13 +337,10 @@ pub(crate) fn declaration_placement( None } else { let options: Bound<'_, PyDict> = PyDict::new(py); - options.set_item( - "kind", - registry::class(py, RecordId::ResourceKind)?.getattr("MODEL")?, - )?; + options.set_item("kind", enum_member(py, RecordId::ResourceKind, "MODEL")?)?; options.set_item("name", model)?; let resource: Bound<'_, PyAny> = - registry::class(py, RecordId::ResourceIdentity)?.call((), Some(&options))?; + new_record(py, RecordId::ResourceIdentity, (), 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 e4de6e0117..b0ae22aa68 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/render/authored_sql.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/render/authored_sql.rs @@ -8,9 +8,9 @@ use pyo3::{pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::boundary::keyword_request::Request; use crate::bindings::_helpers::compile_attachment::sql_expansion_context::SqlExpansionInputs; -use crate::bindings::_helpers::compile_driver::python_calls::call_with; 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::construct::{new_record, new_record_with, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -66,10 +66,9 @@ 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> = registry::class(py, RecordId::ExpansionSpan)?; let objects: Vec> = spans .iter() - .map(|span| span_class.call1(*span)) + .map(|span| new_record(py, RecordId::ExpansionSpan, *span, None)) .collect::>()?; PyTuple::new(py, objects) } @@ -92,10 +91,7 @@ fn declaration_context<'py>( )) }) .collect::>()?; - call_with( - ®istry::class(py, RecordId::DeclarationResolutionContext)?, - &fields, - ) + new_record_with(py, RecordId::DeclarationResolutionContext, &fields) } /// `sql` with natively scanned enum and constant references spliced, and their spans. @@ -119,7 +115,7 @@ fn declarations_expanded<'py>( collection_rendering.clone().unbind(), ); let classes = ExpansionClasses { - usage_record: registry::class(py, RecordId::UsageRecord)?, + usage_record: record_class(py, RecordId::UsageRecord)?, compile_error: registry::error(py, ErrorId::CompileInput)?, }; let path: String = file_path.str()?.extract()?; @@ -280,12 +276,11 @@ fn input_declarations<'py>( file_path: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = file_path.py(); - let resolution_class: Bound<'py, PyAny> = - registry::class(py, RecordId::DeclarationResolutionContext)?; let resolver: &Bound<'py, PyAny> = inputs.declaration_resolver.bind(py); let declarations: Bound<'py, PyAny> = if resolver.is_none() { - call_with( - &resolution_class, + new_record_with( + py, + RecordId::DeclarationResolutionContext, &[ ("enums", inputs.enums.bind(py).clone()), ("constants", inputs.constants.bind(py).clone()), @@ -303,8 +298,9 @@ fn input_declarations<'py>( let merged = |field: &str| -> PyResult> { declarations.getattr(field)?.bitor(local.getattr(field)?) }; - call_with( - &resolution_class, + new_record_with( + py, + RecordId::DeclarationResolutionContext, &[ ("enums", merged("enums")?), ("constants", merged("constants")?), 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 7c4dd7b3fe..b4b83a67bb 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/render/declaration_values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/render/declaration_values.rs @@ -9,6 +9,9 @@ use pyo3::{IntoPyObject, pyclass, 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::construct::{ + enum_member, new_record, new_record_with, record_class, +}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -45,8 +48,8 @@ fn runtime_declarations<'py>( ) -> PyResult> { let py: Python<'py> = discovered.py(); let classes = ( - registry::class(py, RecordId::DeclarationIdentity)?, - registry::class(py, RecordId::DeclarationKind)?, + record_class(py, RecordId::DeclarationIdentity)?, + record_class(py, RecordId::DeclarationKind)?, ); let declarations: Bound<'py, PyDict> = PyDict::new(py); for (files, kind) in [("enum_files", "ENUM"), ("constant_files", "CONSTANT")] { @@ -63,9 +66,7 @@ fn runtime_declarations<'py>( } } } - let resource_identity: Bound<'py, PyAny> = registry::class(py, RecordId::ResourceIdentity)?; - let model_kind: Bound<'py, PyAny> = - registry::class(py, RecordId::ResourceKind)?.getattr("MODEL")?; + let model_kind: Bound<'py, PyAny> = enum_member(py, RecordId::ResourceKind, "MODEL")?; for model_file in native_field(discovered, "model_files")?.try_iter()? { let model_file: Bound<'py, PyAny> = model_file?; let name: Bound<'py, PyAny> = model_file @@ -76,7 +77,8 @@ fn runtime_declarations<'py>( } else { native_field(&model_file, "relative_path")?.getattr("stem")? }; - let owner: Bound<'py, PyAny> = resource_identity.call1((&model_kind, name))?; + let owner: Bound<'py, PyAny> = + new_record(py, RecordId::ResourceIdentity, (&model_kind, name), None)?; for (attribute, kind) in [ ("enum_declarations", "ENUM"), ("constant_declarations", "CONSTANT"), @@ -111,8 +113,7 @@ fn runtime_declarations<'py>( /// Resources whose declarations differ from their directory's: private owners and grantees. fn resource_specific<'py>(scope_index: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = scope_index.py(); - let private: Bound<'py, PyAny> = - registry::class(py, RecordId::ScopeKind)?.getattr("PRIVATE")?; + let private: Bound<'py, PyAny> = enum_member(py, RecordId::ScopeKind, "PRIVATE")?; let mut resources: Vec> = Vec::new(); for declaration in native_field(scope_index, "declarations")?.try_iter()? { let declaration: Bound<'py, PyAny> = declaration?; @@ -140,8 +141,9 @@ pub(crate) fn declaration_scope_resolver<'py>( } else { lookup.clone() }; - let projection: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::DeclarationRuntimeProjection)?, + let projection: Bound<'py, PyAny> = new_record_with( + py, + RecordId::DeclarationRuntimeProjection, &[( "declarations", imported(py, TYPES_MODULE, "MappingProxyType")?.call1((&declarations,))?, @@ -149,8 +151,9 @@ pub(crate) fn declaration_scope_resolver<'py>( )?; let native_contexts: Bound<'py, PyAny> = native_declaration_contexts(&lookup, declarations.as_any())?; - call_with( - ®istry::class(py, RecordId::DeclarationScopeResolver)?, + new_record_with( + py, + RecordId::DeclarationScopeResolver, &[ ("project_dir", native_field(discovered, "project_dir")?), ("lookup", lookup), @@ -206,15 +209,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> = registry::class(py, RecordId::DeclarationKind)?; + let kind_type: Bound<'py, PyAny> = record_class(py, RecordId::DeclarationKind)?; let kinds: Vec> = DECLARATION_KINDS .iter() - .map(|kind| kind_type.getattr(*kind)) + .map(|kind| enum_member(py, RecordId::DeclarationKind, kind)) .collect::>()?; let value_classes: Vec> = vec![ - registry::class(py, RecordId::EnumDeclaration)?, - registry::class(py, RecordId::ConstantDeclaration)?, - registry::class(py, RecordId::LoadedMacro)?, + record_class(py, RecordId::EnumDeclaration)?, + record_class(py, RecordId::ConstantDeclaration)?, + record_class(py, RecordId::LoadedMacro)?, ]; let index: Bound<'py, PyAny> = native_field(lookup, "visibility_index")?; let lookup_declarations: Bound<'py, PyAny> = native_field(lookup, "declarations")?; @@ -243,18 +246,18 @@ pub(crate) fn native_declaration_contexts<'py>( )?); } let reasons: Bound<'py, PyDict> = PyDict::new(py); - for reason in registry::class(py, RecordId::VisibilityReason)?.try_iter()? { + for reason in record_class(py, RecordId::VisibilityReason)?.try_iter()? { let reason: Bound<'py, PyAny> = reason?; reasons.set_item(native_field(&reason, "value")?, reason)?; } let classes: Bound<'py, PyDict> = PyDict::new(py); classes.set_item( "visibility_record", - registry::class(py, RecordId::VisibilityRecord)?, + record_class(py, RecordId::VisibilityRecord)?, )?; classes.set_item( "resolution_context", - registry::class(py, RecordId::DeclarationResolutionContext)?, + record_class(py, RecordId::DeclarationResolutionContext)?, )?; classes.set_item("reasons", reasons)?; let keys: Bound<'py, PyDict> = PyDict::new(py); @@ -284,16 +287,13 @@ pub(crate) fn native_declaration_contexts<'py>( ("resource_identity", RecordId::ResourceIdentity), ("declaration_identity", RecordId::DeclarationIdentity), ] { - lookups.set_item(name, registry::class(py, class)?)?; + lookups.set_item(name, record_class(py, class)?)?; } - lookups.set_item( - "resource_kind", - registry::class(py, RecordId::ResourceKind)?, - )?; + lookups.set_item("resource_kind", record_class(py, RecordId::ResourceKind)?)?; lookups.set_item("declaration_kind", kind_type)?; lookups.set_item( "private_scope", - registry::class(py, RecordId::ScopeKind)?.getattr("PRIVATE")?, + enum_member(py, RecordId::ScopeKind, "PRIVATE")?, )?; lookups.set_item( "identity_error", 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 5111817fb5..5c2ac68218 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 @@ -7,10 +7,13 @@ use pyo3::types::{PyDict, PyDictMethods, PyList, PyString, PyStringMethods, PyTu use pyo3::{pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, keywords, + KeywordArguments, imported, keywords, }; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::construct::{ + enum_member, new_record, new_record_with, record_class, +}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -67,7 +70,7 @@ fn generated_classes(py: Python<'_>) -> PyResult> { &[ ( "compiler_diagnostic", - registry::class(py, RecordId::CompilerDiagnostic)?, + record_class(py, RecordId::CompilerDiagnostic)?, ), ( "report_diagnostic", @@ -75,17 +78,15 @@ fn generated_classes(py: Python<'_>) -> PyResult> { ), ( "source_location", - registry::class(py, RecordId::SourceLocation)?, + record_class(py, RecordId::SourceLocation)?, ), ( "resource_identity", - registry::class(py, RecordId::ResourceIdentity)?, + record_class(py, RecordId::ResourceIdentity)?, ), ( "generic_syntax", - registry::class(py, RecordId::SqlLexicalSyntax)? - .call0()? - .getattr("native_mapping")?, + new_record(py, RecordId::SqlLexicalSyntax, (), None)?.getattr("native_mapping")?, ), ("generated_help", help), ("all_markers", markers), @@ -94,7 +95,7 @@ fn generated_classes(py: Python<'_>) -> PyResult> { } fn built_classes(py: Python<'_>) -> PyResult> { - let declaration_kind: Bound<'_, PyAny> = registry::class(py, RecordId::DeclarationKind)?; + let declaration_kind: Bound<'_, PyAny> = record_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); @@ -129,22 +130,22 @@ fn built_classes(py: Python<'_>) -> PyResult> { ), ( "declaration_identity", - registry::class(py, RecordId::DeclarationIdentity)?, + record_class(py, RecordId::DeclarationIdentity)?, ), ("macro_kind", native_field(&declaration_kind, "MACRO")?), ("declaration_kind", declaration_kind), - ("usage_record", registry::class(py, RecordId::UsageRecord)?), + ("usage_record", record_class(py, RecordId::UsageRecord)?), ( "runtime_usage", - registry::class(py, RecordId::UsageKind)?.getattr("RUNTIME")?, + enum_member(py, RecordId::UsageKind, "RUNTIME")?, ), ( "resource_identity", - registry::class(py, RecordId::ResourceIdentity)?, + record_class(py, RecordId::ResourceIdentity)?, ), ( "model_kind", - registry::class(py, RecordId::ResourceKind)?.getattr("MODEL")?, + enum_member(py, RecordId::ResourceKind, "MODEL")?, ), ( "resource_ref", @@ -188,7 +189,7 @@ fn result_model<'py>( py: Python<'py>, pairs: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - call_with(®istry::class(py, RecordId::MacroExpansionResult)?, pairs) + new_record_with(py, RecordId::MacroExpansionResult, pairs) } /// The macro expansion of `request`'s SQL, or `None` when it calls no macro. @@ -234,12 +235,12 @@ pub(crate) fn expansion_result<'py>(request: ExpansionRequest<'py>) -> PyResult< let Some(output) = macro_output(request)? else { return result_model(py, &[("sql", sql.into_any())]); }; - 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); - spans.push(call_with( - &span_class, + spans.push(new_record_with( + py, + RecordId::ExpansionSpan, &[ ("source_start", source_start.into_pyobject(py)?.into_any()), ("source_end", source_end.into_pyobject(py)?.into_any()), @@ -256,7 +257,7 @@ pub(crate) fn expansion_result<'py>(request: ExpansionRequest<'py>) -> PyResult< .iter() .map(|(kind, name)| format!("{}(\"{name}\")", typed_reference_function(kind))) .collect(); - let syntax: Bound<'py, PyAny> = registry::class(py, RecordId::SqlLexicalSyntax)?.call0()?; + let syntax: Bound<'py, PyAny> = new_record(py, RecordId::SqlLexicalSyntax, (), None)?; extract_references(&calls.join(" "), &syntax)?.into_any() }; result_model( 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 9317352ed2..2b27e8f645 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/builtin_catalogue.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/builtin_catalogue.rs @@ -10,8 +10,8 @@ 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::construct::{new_record, record_class}; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; const FAMILY_SUFFIX_LENGTH: usize = 3; @@ -55,13 +55,15 @@ fn rule<'py>( check: &Bound<'py, PyAny>, metadata: RuleMetadata, ) -> PyResult> { - let option_class: Bound<'py, PyAny> = registry::class(py, RecordId::RuleOption)?; + let option_class: Bound<'py, PyAny> = record_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) => registry::class(py, RecordId::RuleGuidance)?.call( + Some(guidance) => new_record( + py, + RecordId::RuleGuidance, (), Some(&keywords( py, @@ -110,7 +112,7 @@ fn rule<'py>( ("guidance", guidance), ], )?; - registry::class(py, RecordId::Rule)?.call((), Some(&arguments)) + new_record(py, RecordId::Rule, (), Some(&arguments)) } /// Every built-in rule as a `Rule` whose `check` is the given placeholder. @@ -118,7 +120,7 @@ pub(crate) fn builtin_rules<'py>( py: Python<'py>, check: Bound<'py, PyAny>, ) -> PyResult>> { - let subjects: Bound<'py, PyAny> = registry::class(py, RecordId::RuleSubject)?; + let subjects: Bound<'py, PyAny> = record_class(py, RecordId::RuleSubject)?; compiler_guard(|| Ok(catalogue()))? .into_iter() .map(|metadata| rule(py, &subjects, &check, metadata)) 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 85147f2165..e8a10d384a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_evaluation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_evaluation.rs @@ -16,8 +16,9 @@ use sqlbuild_rules::custom_rule_cache::models::CustomEvaluation; 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::imported; use crate::bindings::_helpers::compile_driver::python_calls::keywords; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::records::construct::{new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -175,8 +176,9 @@ pub(crate) fn outcome<'py>( (findings, hits, misses): (Vec>, usize, usize), (custom_ms, custom_cpu_ms): (i64, i64), ) -> PyResult> { - call_with( - ®istry::class(py, RecordId::CustomRulesOutcome)?, + new_record_with( + py, + RecordId::CustomRulesOutcome, &[ ("findings", PyTuple::new(py, findings)?.into_any()), ("cache_hits", hits.into_bound_py_any(py)?), @@ -318,7 +320,7 @@ fn host_project<'py>(project: &Bound<'py, PyAny>) -> PyResult> } changes.set_item( "scope_index", - registry::class(py, RecordId::ScopeIndex)?.call0()?, + new_record(py, RecordId::ScopeIndex, (), None)?, )?; imported(py, "dataclasses", "replace")?.call((record,), Some(&changes)) } 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 832fc8e2da..9ddb7ad7c1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_hosts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_hosts.rs @@ -27,6 +27,7 @@ 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::construct::new_record; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -411,7 +412,6 @@ pub(crate) fn host_payload_project<'py>( project: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = project.py(); - let model_class: Bound<'py, PyAny> = registry::class(py, RecordId::CompiledModel)?; let mut models: Vec> = Vec::new(); for model in native_field(project, "models")?.try_iter()? { let model: Bound<'py, PyAny> = model?; @@ -419,7 +419,7 @@ pub(crate) fn host_payload_project<'py>( for name in PAYLOAD_MODEL_FIELDS { fields.set_item(name, model.getattr(name)?)?; } - models.push(model_class.call((), Some(&fields))?); + models.push(new_record(py, RecordId::CompiledModel, (), Some(&fields))?); } let record: Bound<'py, PyAny> = registry::native(py, NativeAttr::CompiledProjectRecord)?.call1((project,))?; 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 091d27f073..93717f90e7 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/dsl_headers.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/dsl_headers.rs @@ -12,10 +12,9 @@ use sqlbuild_rules::dsl_headers::main::sql_body_ranges::sql_body_ranges as body_ use sqlbuild_rules::dsl_headers::models::{HeaderRulesConfig, HeaderSpan, HeaderViolation}; use crate::bindings::_helpers::boundary::panics::value_error; -use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::lint_pipeline::{LintViolationRow, ViolationClasses}; use crate::bindings::_helpers::rules::native_header_parser::NativeHeaderParser; @@ -71,12 +70,12 @@ fn scan_headers<'py>( first_only: bool, ) -> PyResult> { let py: Python<'py> = contents.py(); - let class: Bound<'py, PyAny> = registry::class(py, RecordId::HeaderSpan)?; let headers: Vec> = scan(&text(contents)?, first_only) .into_iter() .map(|header| { - call_with( - &class, + new_record_with( + py, + RecordId::HeaderSpan, &[ ("kind", PyString::new(py, &header.kind).into_any()), ("start", header.start.into_pyobject(py)?.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/fact_replay.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/fact_replay.rs index ec38b06a57..28e05abf1b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/fact_replay.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/fact_replay.rs @@ -21,10 +21,10 @@ use crate::bindings::_helpers::compiled_project::function_views::NativeCompiledF use crate::bindings::_helpers::compiled_project::project_view::NativeProjectView; use crate::bindings::_helpers::compiled_project::seed_views::NativeCompiledSeed; use crate::bindings::_helpers::compiled_project::source_views::NativeCompiledSource; +use crate::bindings::_helpers::records::construct::record_class; use crate::bindings::_helpers::records::fields::field as record_field_or_attribute; use crate::bindings::_helpers::records::fields::record_spec; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::fact_encoder::{ Encoded, FactEncoder, FactFailure, error_digest, value_digest, }; @@ -806,7 +806,7 @@ fn item_fields<'py>(encoder: &FactEncoder, item: &Bound<'py, PyAny>) -> PyResult .collect::>()?; return Ok((encoder.qualified_name(&class)?, names, values)); }; - let class: Bound<'py, PyAny> = registry::class(py, record)?; + let class: Bound<'py, PyAny> = record_class(py, record)?; let class: &Bound<'py, PyType> = class.downcast::()?; let names: Vec<&'static str> = record_spec(class)? .fields diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/findings.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/findings.rs index 7a459f62a6..201c02078b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/findings.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/findings.rs @@ -1,5 +1,6 @@ //! Rule findings converted between the Python `Finding` model and native rows and payloads. +use crate::bindings::_helpers::records::construct::record_class; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -21,7 +22,7 @@ pub(crate) struct FindingClasses<'py> { impl<'py> FindingClasses<'py> { pub(crate) fn new(py: Python<'py>) -> PyResult { Ok(Self { - finding: registry::class(py, RecordId::Finding)?, + finding: record_class(py, RecordId::Finding)?, path: py.import(PATHLIB_MODULE)?.getattr(PATH_CLASS)?, }) } 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 8fe9e4a7c9..d679428d87 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/fix_facts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/fix_facts.rs @@ -4,11 +4,10 @@ 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; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as record_field; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::fact_replay::project_attribute; /// Index a compilation by model key and diagnostic owner without changing its fact views. @@ -56,8 +55,9 @@ pub(crate) fn compile_facts<'py>( let count: i64 = counts.get_item(&identity)?.extract()?; counts.set_item(identity, count + 1)?; } - call_with( - ®istry::class(py, RecordId::CompileFacts)?, + new_record_with( + 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 f5a82effb1..7acfa28d88 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/fix_session.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/fix_session.rs @@ -16,12 +16,11 @@ use sqlbuild_rules::fix_verification::models::{FixEdit, FixEligibility}; use crate::bindings::_helpers::compile_attachment::sql_expansion_context::SqlExpansionInputs; use crate::bindings::_helpers::compile_driver::project::build_compiled_project; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, keywords, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, keywords}; use crate::bindings::_helpers::discovery::model_file_record::NativeModelFile; use crate::bindings::_helpers::native_host::adapter::AdapterHost; use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::fields::{field, replaced}; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; @@ -657,8 +656,9 @@ fn report<'py>( reason: &str, ) -> PyResult> { let py = source.py(); - call_with( - ®istry::class(py, RecordId::RuleFixResult)?, + new_record_with( + py, + RecordId::RuleFixResult, &[ ("file_path", field(source, "file_path")?), ("code", field(source, "code")?), @@ -687,8 +687,9 @@ fn violation<'py>( details: (&str, &str, &str, &str), ) -> PyResult> { let py = path.py(); - call_with( - ®istry::class(py, RecordId::LintViolation)?, + new_record_with( + 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 8aa4c95f2d..d58ccb5322 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/fix_verdict.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/fix_verdict.rs @@ -8,10 +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; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; 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] @@ -71,8 +70,9 @@ pub(crate) fn fix_verdict<'py>( for (owner, reason) in result.failing { failing.set_item(owners.get_item(owner)?, reason)?; } - call_with( - ®istry::class(py, RecordId::FixVerdict)?, + new_record_with( + py, + RecordId::FixVerdict, &[ ("failing", failing.into_any()), ( 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 724e4ca762..2fc215f8b7 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_headers.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_headers.rs @@ -5,10 +5,9 @@ use pyo3::pyfunction; use pyo3::types::PyBool; use sqlbuild_rules::lint_text::main::named_declaration_role_kind::named_declaration_role_kind; -use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::construct::{enum_from_value, new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; const HOOKS_FOLDER: &str = "hooks"; @@ -28,18 +27,19 @@ pub(crate) fn lint_file_role<'py>( .call_method1("is_relative_to", (project_dir,))? .is_truthy()? { - return registry::class(py, RecordId::LintFileRole)?.call0(); + return new_record(py, RecordId::LintFileRole, (), None); } file_path.call_method1("relative_to", (project_dir,))? } }; let parts: Vec = native_field(&relative, "parts")?.extract()?; let kind: Bound<'py, PyAny> = match named_declaration_role_kind(&parts) { - Some(kind) => registry::class(py, RecordId::DeclarationKind)?.call1((kind,))?, + Some(kind) => enum_from_value(py, RecordId::DeclarationKind, kind)?, None => py.None().into_bound(py), }; - call_with( - ®istry::class(py, RecordId::LintFileRole)?, + new_record_with( + py, + RecordId::LintFileRole, &[ ("declaration_kind", kind), ("in_project", PyBool::new(py, true).to_owned().into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_pipeline.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_pipeline.rs index fe3ae76b86..9c64ee4dc4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_pipeline.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_pipeline.rs @@ -36,6 +36,7 @@ use sqlbuild_rules::sql_lint::types::PreparedSql; use crate::bindings::_helpers::boundary::panics::value_error; use crate::bindings::_helpers::compile_attachment::sql_expansion_context::SqlExpansionInputs; use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::construct::{new_record_with, record_class}; use crate::bindings::_helpers::records::fields::field as record_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -317,9 +318,9 @@ pub(crate) struct ViolationClasses<'py> { impl<'py> ViolationClasses<'py> { pub(crate) fn new(py: Python<'py>) -> PyResult { Ok(Self { - violation: registry::class(py, RecordId::LintViolation)?, - edit: registry::class(py, RecordId::LintEdit)?, - severity: registry::class(py, RecordId::LintSeverity)?, + violation: record_class(py, RecordId::LintViolation)?, + edit: record_class(py, RecordId::LintEdit)?, + severity: record_class(py, RecordId::LintSeverity)?, }) } @@ -416,8 +417,9 @@ impl LintOutcome { for (file, message) in &self.unexpandable { unexpandable.set_item(&paths[*file], message)?; } - call_with( - ®istry::class(py, RecordId::LintRunResult)?, + new_record_with( + py, + RecordId::LintRunResult, &[ ( "files_checked", 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 8e063a9d8e..155fe9f742 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_preparation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_preparation.rs @@ -6,10 +6,10 @@ use pyo3::types::{PyBool, PyDict, PyTuple}; use sqlbuild_rules::lint_text::models::{ExpansionSpan, LintSqlBody}; use crate::bindings::_helpers::compile_attachment::sql_expansion_context::SqlExpansionInputs; -use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; +use crate::bindings::_helpers::records::construct::{new_record_with, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::lint_pipeline::{ BodyRole, SingleBody, SingleBodyRequest, prepare_single_body, }; @@ -36,7 +36,7 @@ fn pass_objects<'py>( py: Python<'py>, passes: &[Vec], ) -> PyResult> { - let class: Bound<'py, PyAny> = registry::class(py, RecordId::ExpansionSpan)?; + let class: Bound<'py, PyAny> = record_class(py, RecordId::ExpansionSpan)?; let passes: Vec> = passes .iter() .map(|spans| span_objects(&class, spans)) @@ -86,8 +86,9 @@ pub(crate) fn prepare_lint_body<'py>( })?; let body: &LintSqlBody = &prepared.body; let flag = |value: bool| PyBool::new(py, value).to_owned().into_any(); - call_with( - ®istry::class(py, RecordId::LintBody)?, + new_record_with( + py, + RecordId::LintBody, &[ ("file_path", file_path.clone()), ("body_start", body.body_start.into_pyobject(py)?.into_any()), 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 0183cc8d3b..e64fc28b0e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_text.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_text.rs @@ -22,10 +22,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; +use crate::bindings::_helpers::records::construct::{ + enum_from_value, enum_member, new_record_with, +}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::lint_pipeline::{ LintViolationRow, NATIVE_ENGINE, ViolationClasses, }; @@ -130,9 +131,11 @@ fn named_declaration_role_kind<'py>( ) -> PyResult>> { let parts: Vec = native_field(relative_path, "parts")?.extract()?; match role_kind(&parts) { - Some(kind) => Ok(Some( - registry::class(relative_path.py(), RecordId::DeclarationKind)?.call1((kind,))?, - )), + Some(kind) => Ok(Some(enum_from_value( + relative_path.py(), + RecordId::DeclarationKind, + kind, + )?)), None => Ok(None), } } @@ -232,15 +235,12 @@ fn format_lint_sql_bodies<'py>( for (index, text) in result.formatted { formatted.set_item(&files[index].0, text)?; } - let violation: Bound<'py, PyAny> = registry::class(py, RecordId::LintViolation)?; - let fault: Bound<'py, PyAny> = registry::class(py, RecordId::LintSeverity)?.getattr("FAULT")?; + let fault: Bound<'py, PyAny> = enum_member(py, RecordId::LintSeverity, "FAULT")?; let faults: Vec> = result .faults .into_iter() .map(|item| { - call_with( - &violation, - &[ + new_record_with(py, RecordId::LintViolation, &[ ("file_path", files[item.file].0.clone()), ("line", item.line.into_pyobject(py)?.into_any()), ("column", 1_usize.into_pyobject(py)?.into_any()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/rows.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/rows.rs index bd58de2122..63acdfc643 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/rows.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/rows.rs @@ -34,6 +34,7 @@ use crate::bindings::_helpers::compiled_project::project::NativeCompiledProject; use crate::bindings::_helpers::compiled_project::records::compiled_project_facts; use crate::bindings::_helpers::compiled_project::records::declaration_rows; use crate::bindings::_helpers::compiled_project::values::plain_value; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -249,8 +250,9 @@ fn evaluated<'py>( .into_iter() .map(|fault| classes.finding_of_fault(fault, false)) .collect::>()?; - call_with( - ®istry::class(py, RecordId::NativeRulesEvaluation)?, + new_record_with( + 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 0366b908fd..051bdc54f4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_api.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_api.rs @@ -17,6 +17,7 @@ use sqlbuild_rules::project_tree::main::project_tree_entries::project_tree_entri use sqlbuild_rules::project_tree::models::ProjectTreeEntry; use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::records::construct::new_record; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -152,7 +153,6 @@ fn escapes(path: &Bound<'_, PyAny>) -> 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> = registry::class(py, RecordId::ProjectPath)?; let mut paths: Vec<(Bound<'py, PyAny>, bool)> = project_tree_entries(&root) .into_iter() .map(|ProjectTreeEntry { relative, is_file }| { @@ -179,7 +179,7 @@ fn project_tree_paths<'py>(root: &Bound<'py, PyAny>) -> PyResult>()?; PyTuple::new(py, nodes) 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 73ef523fab..cecaca1e14 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_dispatch.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_dispatch.rs @@ -20,6 +20,7 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ }; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -351,8 +352,9 @@ fn evaluate_native<'py>( let native_misses: i64 = field(&native_result, "cache_misses")?.extract()?; let custom_hits: i64 = field(&custom, "cache_hits")?.extract()?; let custom_misses: i64 = field(&custom, "cache_misses")?.extract()?; - call_with( - ®istry::class(py, RecordId::RulesResult)?, + new_record_with( + py, + RecordId::RulesResult, &[ ("findings", findings), ( 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 126272ba37..28af3ea949 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_plan.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_plan.rs @@ -10,6 +10,7 @@ use crate::bindings::_helpers::boundary::keyword_request::Request; use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::{field, replaced}; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -97,8 +98,9 @@ pub(crate) fn plan_rule_run<'py>( ("ignore", PyTuple::empty(py).into_any()), ], )?; - call_with( - ®istry::class(py, RecordId::RuleRunPlan)?, + new_record_with( + py, + RecordId::RuleRunPlan, &[ ("effective_config", effective_config), ("project_dir", project_dir), 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 e8f70c1d5a..5f53eaf5ca 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_run.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_run.rs @@ -12,11 +12,11 @@ use sqlbuild_rules::rule_results::main::group_unevaluated::group_unevaluated; 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::compile_driver::python_calls::imported; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as record_field; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::fact_replay::project_models; use crate::bindings::_helpers::rules::results::rule_finding; use crate::bindings::_helpers::rules::rows::finalize_rule_findings_rows; @@ -29,8 +29,9 @@ fn integer(object: &Bound<'_, PyAny>, field: &str) -> PyResult { } fn python_finding<'py>(py: Python<'py>, finding: RuleFinding) -> PyResult> { - call_with( - ®istry::class(py, RecordId::Finding)?, + new_record_with( + py, + RecordId::Finding, &[ ("code", finding.code.into_bound_py_any(py)?), ( @@ -97,8 +98,9 @@ pub(crate) fn finish_rule_run<'py>( let total = |field: &str| -> PyResult> { (integer(result, field)? + integer(sql_result, field)?).into_bound_py_any(py) }; - call_with( - ®istry::class(py, RecordId::RulesRunResult)?, + new_record_with( + 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 3b8c057520..784f7ef243 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/rules_config.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/rules_config.rs @@ -13,6 +13,7 @@ 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; +use crate::bindings::_helpers::records::construct::new_record; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -30,7 +31,7 @@ impl<'py> Models<'py> { for (name, value) in fields { arguments.set_item(name, value)?; } - registry::class(self.py, class)?.call((), Some(&arguments)) + new_record(self.py, class, (), Some(&arguments)) } } 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 0d996c3266..312da3c8d9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/sql_rules.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/sql_rules.rs @@ -18,10 +18,11 @@ use sqlbuild_rules::rule_results::models::RuleFinding; use crate::bindings::_helpers::analysis::model_reads::output_proven; use crate::bindings::_helpers::boundary::keyword_request::Request; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; +use crate::bindings::_helpers::compile_driver::python_calls::{imported, keywords}; use crate::bindings::_helpers::compiled_project::compiled_model_view::{ NativeCompiledModel, SqlRuleInputs, SqlRuleKey, }; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; @@ -156,8 +157,9 @@ pub(crate) fn run_planned_sql_rules<'py>( let (findings, hits, misses) = 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( - ®istry::class(py, RecordId::SqlRulesEvaluation)?, + new_record_with( + py, + RecordId::SqlRulesEvaluation, &[ ("findings", finding_objects(py, findings)?.into_any()), ("cache_hits", hits.into_bound_py_any(py)?), @@ -169,13 +171,13 @@ pub(crate) fn run_planned_sql_rules<'py>( /// Finding rows as `Finding` records. fn finding_objects(py: Python<'_>, findings: Vec) -> PyResult> { - let class: Bound<'_, PyAny> = registry::class(py, RecordId::Finding)?; let path_class: Bound<'_, PyAny> = imported(py, PATHLIB_MODULE, "Path")?; let objects: Vec> = findings .into_iter() .map(|finding| { - call_with( - &class, + new_record_with( + py, + RecordId::Finding, &[ ("code", PyString::new(py, &finding.code).into_any()), ("path", path_class.call1((finding.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 d81d234335..963703505b 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::construct::record_class; use crate::bindings::_helpers::records::fields::field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; const DESCRIPTION_KEY: &str = "description"; @@ -88,7 +88,7 @@ fn tested_resources(test: &Bound<'_, PyAny>) -> PyResult> { fn with_payload(test: &Bound<'_, PyAny>, mut fact: SqlTestFact) -> PyResult { let payload: Bound<'_, PyAny> = field(test, "payload")?; let model_payload: Bound<'_, PyAny> = - registry::class(test.py(), RecordId::CompiledModelSqlTestPayload)?; + record_class(test.py(), RecordId::CompiledModelSqlTestPayload)?; if !payload.is_instance(&model_payload)? { return Ok(fact); } diff --git a/tests/integration/src/sqlbuild/rule_engine/host_payload/_test_types.py b/tests/integration/src/sqlbuild/rule_engine/host_payload/_test_types.py index 63224ceda7..f3e7a28947 100644 --- a/tests/integration/src/sqlbuild/rule_engine/host_payload/_test_types.py +++ b/tests/integration/src/sqlbuild/rule_engine/host_payload/_test_types.py @@ -5,4 +5,5 @@ class HostPayloadFactsTestCase: description: str project_files: dict[str, str] - expected_unpopulated_trimmed_fields: frozenset[str] + expected_dropped_project_fields: frozenset[str] + expected_dropped_model_fields: frozenset[str] diff --git a/tests/integration/src/sqlbuild/rule_engine/host_payload/helpers.py b/tests/integration/src/sqlbuild/rule_engine/host_payload/helpers.py index 67ba931bbd..a4d204b5da 100644 --- a/tests/integration/src/sqlbuild/rule_engine/host_payload/helpers.py +++ b/tests/integration/src/sqlbuild/rule_engine/host_payload/helpers.py @@ -1,15 +1,8 @@ import dataclasses -from collections.abc import Callable from itertools import product from pathlib import Path -from typing import Any -import pytest - -import sqlbuild._native as compile_models from sqlbuild._native import ( - CompiledModel, - CompiledProject, Model, build_compiled_project, custom_rule_fact_digest, @@ -160,26 +153,3 @@ def fact_digests(*, project: CompiledProjectView, root: Path) -> dict[FactKey, s key: custom_rule_fact_digest(project=project, project_dir=root, key=key) for key in every_fact_key(project) } - - -def record_trimmed_fields( - monkeypatch: pytest.MonkeyPatch, -) -> dict[type[object], set[str]]: - """Record replaced fields and omitted constructor fields while the host payload is trimmed.""" - - trimmed: dict[type[object], set[str]] = {CompiledProject: set(), CompiledModel: set()} - replace: Callable[..., Any] = dataclasses.replace - - def recording_replace(obj: Any, /, **changes: object) -> Any: - trimmed.setdefault(type(obj), set()).update(changes) - return replace(obj, **changes) - - def recording_model(**values: Any) -> CompiledModel: - trimmed[CompiledModel].update( - {field.name for field in dataclasses.fields(CompiledModel)} - values.keys() - ) - return CompiledModel(**values) - - monkeypatch.setattr(dataclasses, "replace", recording_replace) - monkeypatch.setattr(compile_models, "CompiledModel", recording_model) - return trimmed diff --git a/tests/integration/src/sqlbuild/rule_engine/host_payload/test_host_payload_facts.py b/tests/integration/src/sqlbuild/rule_engine/host_payload/test_host_payload_facts.py index 69a2e08d13..6d50b35423 100644 --- a/tests/integration/src/sqlbuild/rule_engine/host_payload/test_host_payload_facts.py +++ b/tests/integration/src/sqlbuild/rule_engine/host_payload/test_host_payload_facts.py @@ -5,8 +5,6 @@ import pytest from sqlbuild._native import ( - CompiledModel, - CompiledProject, compiled_project_record, custom_host_payload, ) @@ -20,7 +18,6 @@ compile_files, dropped_fields, fact_digests, - record_trimmed_fields, ) @@ -104,18 +101,45 @@ "SELECT 1\n" ), }, - expected_unpopulated_trimmed_fields=frozenset(), + expected_dropped_project_fields=frozenset( + { + "binding_catalog", + "diagnostics", + "external_sql_reference_resolver", + "hook_functions", + "loaded_macros", + "loader_functions", + "materialization_files", + "models", + "native_project", + "scope_index", + "sql_expansions", + "sql_hook_files", + "sql_scenarios", + "unmatched_literal_sql_relations", + } + ), + expected_dropped_model_fields=frozenset( + { + "authored_query_sql", + "binding_diagnostics", + "enum_columns", + "fast_lineage_columns", + "macro_deps", + "output_column_locations", + "references", + "unchecked_output_columns", + } + ), ), ), ids=lambda case: case.description, ) def test_given_compiled_project_when_trimming_host_payload_then_every_fact_is_unchanged( - tmp_path: Path, monkeypatch: pytest.MonkeyPatch, test_case: HostPayloadFactsTestCase + tmp_path: Path, test_case: HostPayloadFactsTestCase ) -> None: project: CompiledProjectView = compile_files(root=tmp_path, files=test_case.project_files) - trimmed_fields: dict[type[object], set[str]] = record_trimmed_fields(monkeypatch) trimmed: CompiledProjectView = custom_host_payload(project) - monkeypatch.undo() full_digests: dict[FactKey, str | None] = fact_digests(project=project, root=tmp_path) trimmed_digests: dict[FactKey, str | None] = fact_digests(project=trimmed, root=tmp_path) @@ -124,7 +148,7 @@ def test_given_compiled_project_when_trimming_host_payload_then_every_fact_is_un assert tuple(map(public_model, trimmed.models)) == tuple(map(public_model, project.models)) assert ( dropped_fields(before=compiled_project_record(project), after=trimmed) - == trimmed_fields[CompiledProject] - test_case.expected_unpopulated_trimmed_fields + == test_case.expected_dropped_project_fields ) assert ( frozenset().union( @@ -133,5 +157,5 @@ def test_given_compiled_project_when_trimming_host_payload_then_every_fact_is_un for model, trimmed_model in zip(project.models, trimmed.models, strict=True) ) ) - == trimmed_fields[CompiledModel] - test_case.expected_unpopulated_trimmed_fields + == test_case.expected_dropped_model_fields ) From e6dd716ed673028f40ee4d1aa24dc4261c7301fa Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Sat, 17 Oct 2026 02:18:27 +0100 Subject: [PATCH 05/28] =?UTF-8?q?refactor:=20construct=20native=20records?= =?UTF-8?q?=20directly=20in=20macro=5Fbridge=20=E2=80=A6=20workspace=5Fcom?= =?UTF-8?q?mands=20bindings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/bindings/_helpers/manifest/rows.rs | 6 +-- .../_helpers/model_config/contract_classes.rs | 6 +-- .../project_assembly/declared_relations.rs | 9 ++-- .../project_assembly/syntax_validation.rs | 7 ++-- .../_helpers/scenario_command/conversions.rs | 9 ++-- .../_helpers/scenario_command/setup.rs | 6 +-- .../_helpers/scopes/compile_usages.rs | 10 +++-- .../_helpers/scopes/relationship_names.rs | 9 +++- .../bindings/_helpers/scopes/scope_command.rs | 6 +-- .../bindings/_helpers/scopes/scope_errors.rs | 5 ++- .../bindings/_helpers/scopes/scope_loading.rs | 9 ++-- .../bindings/_helpers/scopes/scope_objects.rs | 6 +-- .../_helpers/scopes/scope_validation.rs | 4 +- .../bindings/_helpers/seed_command/command.rs | 9 ++-- .../_helpers/semantic_checks/inputs.rs | 9 +++- .../_helpers/semantic_checks/resource_sql.rs | 7 ++-- .../_helpers/semantic_checks/stage_objects.rs | 16 +++---- .../_helpers/semantic_shapes/shapes.rs | 7 ++-- .../semantic_shapes/source_bindings.rs | 20 +++++---- .../_helpers/sql_test_command/test_command.rs | 14 ++++--- .../_helpers/sql_test_glue/compiled_tests.rs | 18 ++++---- .../_helpers/sql_test_glue/cursor_window.rs | 6 +-- .../sql_test_glue/fixture_validation.rs | 4 +- .../_helpers/sql_test_glue/plan_assembly.rs | 20 +++++---- .../_helpers/sql_test_glue/plan_driver.rs | 10 ++--- .../_helpers/sql_test_glue/plan_entries.rs | 6 +-- .../_helpers/sql_test_glue/plan_errors.rs | 3 +- .../_helpers/sql_test_glue/plan_rows.rs | 31 ++++++++------ .../_helpers/sql_test_glue/plan_values.rs | 4 +- .../_helpers/sqltext/authored_values.rs | 6 +-- .../_helpers/sqltext/sql_references.rs | 42 +++++++++++-------- .../bindings/_helpers/state_rows/freshness.rs | 5 ++- .../state_rows/migration_projections.rs | 9 ++-- .../_helpers/state_rows/migration_rows.rs | 29 +++++++------ .../_helpers/type_system/nullability.rs | 6 +-- .../_helpers/workspace_commands/commands.rs | 5 ++- 36 files changed, 211 insertions(+), 167 deletions(-) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/manifest/rows.rs b/crates/sqlbuild-python/src/bindings/_helpers/manifest/rows.rs index 5e352f28b4..f878efbcc5 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/manifest/rows.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/manifest/rows.rs @@ -13,9 +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::construct::record_class; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; const DECIMAL_KIND: &str = "decimal"; const MACRO_KIND: &str = "macro"; @@ -149,8 +149,8 @@ fn hooks(value: &Bound<'_, PyAny>) -> PyResult>> { if !(value.is_instance_of::() || value.is_instance_of::()) { return Ok(None); } - let sql_class: Bound<'_, PyAny> = registry::class(value.py(), RecordId::SqlHookEntry)?; - let python_class: Bound<'_, PyAny> = registry::class(value.py(), RecordId::PythonHookEntry)?; + let sql_class: Bound<'_, PyAny> = record_class(value.py(), RecordId::SqlHookEntry)?; + let python_class: Bound<'_, PyAny> = record_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/model_config/contract_classes.rs b/crates/sqlbuild-python/src/bindings/_helpers/model_config/contract_classes.rs index 292d31795e..c43f5a614a 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 @@ -7,9 +7,9 @@ use pyo3::types::{PyDict, PyDictMethods}; use crate::bindings::_helpers::native_host::facade::FacadeHost; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::native_host::third_party::ThirdPartyHost; +use crate::bindings::_helpers::records::construct::record_class; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; const CONTRACT_CLASSES: [(&str, RecordId); 6] = [ ("schema_column", RecordId::SchemaColumn), @@ -75,7 +75,7 @@ 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, class) in CONTRACT_CLASSES { - classes.set_item(key, registry::class(py, class)?)?; + classes.set_item(key, record_class(py, class)?)?; } classes.set_item( "severities", @@ -92,7 +92,7 @@ fn built_yaml_entry_classes(py: Python<'_>) -> PyResult> { let classes: Bound<'_, PyDict> = PyDict::new(py); classes.set_item("contracts", contract_classes(py)?)?; for (key, class) in YAML_ENTRY_CLASSES { - classes.set_item(key, registry::class(py, class)?)?; + classes.set_item(key, record_class(py, class)?)?; } for (key, class) in INTEGRATION_ENTRY_CLASSES { classes.set_item(key, host::entry(py, class)?)?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/project_assembly/declared_relations.rs b/crates/sqlbuild-python/src/bindings/_helpers/project_assembly/declared_relations.rs index e68586513e..baff313cf2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/project_assembly/declared_relations.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/project_assembly/declared_relations.rs @@ -8,9 +8,9 @@ use crate::bindings::_helpers::compiled_project::model_input_view::{ attached_columns, attached_schema, }; use crate::bindings::_helpers::compiled_project::schema_facts::python_column_facts; +use crate::bindings::_helpers::records::construct::{enum_member, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; const UNKNOWN_TYPE: &str = "UNKNOWN"; const NOT_NULL_AUDIT_NAME: &str = "not_null"; @@ -77,7 +77,7 @@ pub(crate) fn declared_relations<'py>( inputs: Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = inputs.py(); - let nullability: Bound<'py, PyAny> = registry::class(py, RecordId::InferredNullability)?; + let nullability: Bound<'py, PyAny> = record_class(py, RecordId::InferredNullability)?; let types: Bound<'py, PyDict> = PyDict::new(py); let nulls: Bound<'py, PyDict> = PyDict::new(py); let complete: Bound<'py, PyDict> = PyDict::new(py); @@ -140,7 +140,10 @@ pub(crate) fn declared_relations<'py>( types.set_item(&name, column_types(py, &columns, true)?)?; let unknown: Bound<'py, PyDict> = PyDict::new(py); for column in &columns { - unknown.set_item(&column.name, nullability.getattr(UNKNOWN_MEMBER)?)?; + unknown.set_item( + &column.name, + enum_member(py, RecordId::InferredNullability, UNKNOWN_MEMBER)?, + )?; } nulls.set_item(&name, unknown)?; complete.set_item(&name, column_types(py, &columns, true)?)?; 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 b8ded37b92..dc5226d709 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,6 +11,7 @@ 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::construct::record_class; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -162,8 +163,8 @@ pub(crate) fn validate_hook_sql_syntax( model_name: &model_name, placeholders: &placeholders, dialect: &dialect, - sql_hook_entry: registry::class(py, RecordId::SqlHookEntry)?, - python_hook_entry: registry::class(py, RecordId::PythonHookEntry)?, + sql_hook_entry: record_class(py, RecordId::SqlHookEntry)?, + python_hook_entry: record_class(py, RecordId::PythonHookEntry)?, }; check_hook_value( &context, @@ -229,7 +230,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> = registry::class(value.py(), RecordId::SqlHookEntry)?; + let sql_hook_entry: Bound<'_, PyAny> = record_class(value.py(), RecordId::SqlHookEntry)?; hook_statements(&value, &sql_hook_entry) } 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 6c506050f9..16552d5e8a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/conversions.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/conversions.rs @@ -18,9 +18,9 @@ use sqlbuild_core::scenario_command::models::{ 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::construct::enum_from_value; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; const SUCCESS_STATUS: &str = "success"; const FAILED_STATUS: &str = "failed"; @@ -93,13 +93,16 @@ 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> = registry::class(py, RecordId::ScenarioArtifactKind)?; items(&native_field(relation_map, "artifacts")?)? .iter() .map(|artifact| { let identity: Bound<'_, PyAny> = native_field(artifact, "identity")?; Ok(RetainedRelation { - kind: enum_value(&kinds.call1((native_field(&identity, "kind")?,))?)?, + kind: enum_value(&enum_from_value( + py, + RecordId::ScenarioArtifactKind, + native_field(&identity, "kind")?, + )?)?, logical_name: native_field(&identity, "logical_name")?.extract()?, physical_name: native_field(artifact, "physical_name")?.extract()?, }) 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 cd6065e701..09853d2f3b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/setup.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/setup.rs @@ -36,6 +36,7 @@ use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_selection::unit_tests::reject_unit_test_selectors; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -292,10 +293,7 @@ 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( - ®istry::class(py, RecordId::CompilePipelineOptions)?, - &pairs, - )?; + let options: Bound<'py, PyAny> = new_record_with(py, RecordId::CompilePipelineOptions, &pairs)?; let lifecycle: Bound<'py, PyAny> = call_with( &host::entry(py, ExecutorHost::OperationLifecycle)?, &[ 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 8858b1a1c9..6c423b42d9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/compile_usages.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/compile_usages.rs @@ -19,6 +19,7 @@ use sqlbuild_scopes::scope_index::models::{ }; use crate::bindings::_helpers::compiled_project::model_loop_named::scope_path_message; +use crate::bindings::_helpers::records::construct::new_record; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -150,13 +151,12 @@ pub(crate) fn model_schema_scope_usages<'py>( )); } report_inaccessible(py, diagnostics)?; - 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); fields.set_item("consumer", native_field(&records[consumer], "identity")?)?; fields.set_item("declaration", native_field(&records[used], "identity")?)?; - built.push(usage_record.call((), Some(&fields))?); + built.push(new_record(py, RecordId::UsageRecord, (), Some(&fields))?); } PyTuple::new(py, built) } @@ -190,7 +190,6 @@ fn report_inaccessible(py: Python<'_>, diagnostics: Vec) -> PyRes return Ok(()); } 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); @@ -202,7 +201,10 @@ fn report_inaccessible(py: Python<'_>, diagnostics: Vec) -> PyRes fields.set_item("help", help)?; let arguments: Bound<'_, PyDict> = PyDict::new(py); arguments.set_item("key", PyTuple::new(py, key)?)?; - arguments.set_item("diagnostic", compiler_diagnostic.call((), Some(&fields))?)?; + arguments.set_item( + "diagnostic", + new_record(py, RecordId::CompilerDiagnostic, (), Some(&fields))?, + )?; report.call((), Some(&arguments))?; } Ok(()) 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 ecce43864f..e12cfba468 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/relationship_names.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/relationship_names.rs @@ -13,6 +13,7 @@ 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::construct::new_record; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sqltext::lexical_syntax::LexicalSyntaxInput; @@ -108,13 +109,17 @@ fn extract_sql_test_ctes<'py>( return Err(PyErr::from_value(error)); } }; - 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); arguments.set_item("name", name)?; arguments.set_item("sql_body", body)?; - built.push(cte_class.call((), Some(&arguments))?); + built.push(new_record( + py, + RecordId::CompileSqlTestCte, + (), + Some(&arguments), + )?); } PyTuple::new(py, built) } 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 ac95e6dfb9..045c9db4b2 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,7 @@ 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::construct::{enum_from_value, new_record}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -160,10 +161,9 @@ fn report_filters<'py>( py: Python<'py>, request: &Bound<'py, PyAny>, ) -> PyResult> { - let kind_class: Bound<'_, PyAny> = registry::class(py, RecordId::DeclarationKind)?; let mut kinds: Vec> = Vec::new(); for kind in native_field(request, "kinds")?.try_iter()? { - kinds.push(kind_class.call1((kind?,))?); + kinds.push(enum_from_value(py, RecordId::DeclarationKind, kind?)?); } let options: Bound<'_, PyDict> = keywords( py, @@ -183,7 +183,7 @@ fn report_filters<'py>( ("globals", native_field(request, "globals")?), ], )?; - registry::class(py, RecordId::ScopeReportFilters)?.call((), Some(&options)) + new_record(py, RecordId::ScopeReportFilters, (), Some(&options)) } /// `dataclasses.replace(report, **changes)`. 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 663f1e4a99..9fab1e770d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_errors.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_errors.rs @@ -1,5 +1,6 @@ //! Report a scope index's error findings as individual compile diagnostics. +use crate::bindings::_helpers::records::construct::new_record; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -12,7 +13,6 @@ const ERROR_SEVERITY: &str = "error"; pub(crate) fn report_scope_index_errors(index: &Bound<'_, PyAny>) -> PyResult<()> { let py: Python<'_> = index.py(); 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 native_field(index, "diagnostics")?.try_iter()? { let item: Bound<'_, PyAny> = item?; @@ -53,7 +53,8 @@ pub(crate) fn report_scope_index_errors(index: &Bound<'_, PyAny>) -> PyResult<() } fields.set_item("line", native_field(&item, "line")?)?; fields.set_item("column", native_field(&item, "column")?)?; - let diagnostic: Bound<'_, PyAny> = compiler_diagnostic.call((), Some(&fields))?; + let diagnostic: Bound<'_, PyAny> = + new_record(py, RecordId::CompilerDiagnostic, (), Some(&fields))?; let arguments: Bound<'_, PyDict> = PyDict::new(py); arguments.set_item("key", key)?; arguments.set_item("diagnostic", diagnostic)?; 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 7ea3883b93..da1ef161b3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_loading.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_loading.rs @@ -20,9 +20,9 @@ use crate::bindings::_helpers::frontier::compile_frontier::{FrontierStage, bridg 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::construct::{new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::scopes::scope_cache::{ project_fingerprint, read_scope_index_cache, write_scope_index_cache, }; @@ -82,8 +82,9 @@ fn compiled_index<'py>( let collection_rendering: Bound<'py, PyAny> = resolve_effective_collection_rendering(&project_config, &py.None().into_bound(py)) .and_then(|value| value.into_bound_py_any(py))?; - let adapter_context: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::CompileAdapterContext)?, + let adapter_context: Bound<'py, PyAny> = new_record_with( + py, + RecordId::CompileAdapterContext, &[ ("value_renderer", adapter.clone()), ("collection_rendering", collection_rendering), @@ -186,7 +187,7 @@ fn load_or_build_scope_index<'py>( return Ok(cached.into_bound(py)); } } - let mut syntax: Bound<'py, PyAny> = registry::class(py, RecordId::SqlLexicalSyntax)?.call0()?; + let mut syntax: Bound<'py, PyAny> = new_record(py, RecordId::SqlLexicalSyntax, (), None)?; let compiled: PyResult> = match project_adapter(&resolved) { Ok((discovered, adapter)) => { syntax = native_field(&adapter, "sql_lexical_syntax")?; 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 60fd2ec87e..b2f11f9eb8 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_objects.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_objects.rs @@ -17,9 +17,9 @@ use sqlbuild_scopes::scope_report::models::{ ProjectionGrant, ProjectionIndex, ProjectionStanding, ProjectionUsage, ReportValues, }; +use crate::bindings::_helpers::records::construct::{new_record, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; 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, @@ -41,12 +41,12 @@ impl<'py> ScopeClasses<'py> { /// One scope model class. pub(crate) fn model(&self, name: RecordId) -> PyResult> { - registry::class(self.py, name) + record_class(self.py, name) } /// The member of scope enum `name` with `value`; an unknown value raises `ValueError`. pub(crate) fn member(&self, name: RecordId, value: &str) -> PyResult> { - registry::class(self.py, name)?.call1((value,)) + new_record(self.py, name, (value,), None) } /// The interpreter the classes belong to. 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 4e656bcc32..880323d8e0 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,7 @@ 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::construct::enum_member; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -384,8 +385,7 @@ 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> = - registry::class(py, RecordId::ScopeDiagnosticSeverity)?.getattr("ERROR")?; + let error: Bound<'_, PyAny> = enum_member(py, RecordId::ScopeDiagnosticSeverity, "ERROR")?; let mut errors: Vec> = Vec::new(); for item in native_field(index, "diagnostics")?.try_iter()? { let item: Bound<'_, PyAny> = item?; 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 af12efe4f3..7ed6740113 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/seed_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/seed_command/command.rs @@ -28,9 +28,9 @@ use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_entries::command_plans::build_seed_command_plan; use crate::bindings::_helpers::planner_entries::command_plans::read_selected_seed_fingerprints; +use crate::bindings::_helpers::records::construct::{new_record_with, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::result_output::documents::format_seed_execution_json; use crate::bindings::_helpers::result_output::documents::write_execution_json_output; use crate::bindings::_helpers::result_output::event_writer::ExecutionEventWriter; @@ -209,8 +209,9 @@ fn compile_seeds<'py>( native_field(invocation, "effective_project_dir")?, discovered.clone(), )?; - let options: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::CompilePipelineOptions)?, + let options: Bound<'py, PyAny> = new_record_with( + py, + RecordId::CompilePipelineOptions, &[ ("selected_target", native_field(request, "selected_target")?), ("select", native_field(request, "select")?), @@ -437,7 +438,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, ®istry::class(py, RecordId::SeedInvocation)?)?; + resolve_command_invocation(request, &record_class(py, RecordId::SeedInvocation)?)?; let pipeline: Bound<'_, PyAny> = compile_seeds(request, &invocation)?; let requested: Bound<'_, PyAny> = native_field(request, "concurrency")?; let concurrency: i64 = if requested.is_none() { 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 47ba40d9f2..62f6231ee8 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/inputs.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/inputs.rs @@ -7,6 +7,7 @@ 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::construct::new_record; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -57,14 +58,18 @@ pub(crate) fn sql_test_columns<'py>( if bodies.is_empty() { return Ok(columns); } - 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); arguments.set_item("sql", sql)?; arguments.set_item("mode", "batch")?; arguments.set_item("recover_cte_facts", false)?; - queries.push(query_class.call((), Some(&arguments))?); + queries.push(new_record( + py, + RecordId::NativeColumnQuery, + (), + Some(&arguments), + )?); } let arguments: Bound<'py, PyDict> = PyDict::new(py); arguments.set_item("queries", PyTuple::new(py, queries)?)?; 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 f999653e4a..c600d6f720 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 @@ -16,6 +16,7 @@ use sqlbuild_analysis::semantic_checks::models::{ use crate::bindings::_helpers::boundary::panics::compiler_error; use crate::bindings::_helpers::compiled_project::audit_views::audit_reference_rows; use crate::bindings::_helpers::compiled_project::model_loop_host::ReferenceRow; +use crate::bindings::_helpers::records::construct::record_class; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -55,8 +56,8 @@ pub(crate) fn resource_sql_diagnostics_of<'py>( Err(failure) => return Err(failure_error(py, failure)?), }; let classes = DiagnosticClasses { - compiler_diagnostic: registry::class(py, RecordId::CompilerDiagnostic)?, - source_location: registry::class(py, RecordId::SourceLocation)?, + compiler_diagnostic: record_class(py, RecordId::CompilerDiagnostic)?, + source_location: record_class(py, RecordId::SourceLocation)?, path: py.import("pathlib")?.getattr("Path")?, }; let built: Vec> = diagnostics @@ -72,7 +73,7 @@ fn request( profile: &Bound<'_, PyAny>, ) -> PyResult { let py: Python<'_> = project.py(); - let reference_class: Bound<'_, PyAny> = registry::class(py, RecordId::CompileSqlReference)?; + let reference_class: Bound<'_, PyAny> = record_class(py, RecordId::CompileSqlReference)?; let mut audits: Vec = Vec::new(); for audit in native_field(project, "audits")?.try_iter()? { audits.push(audit_input(&audit?, &reference_class)?); 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 4d3645ec91..ad2b999942 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 @@ -2,6 +2,7 @@ use crate::bindings::_helpers::native_host::adapter::AdapterHost; use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::construct::{enum_member, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -40,15 +41,14 @@ pub(crate) struct StageClasses<'py> { impl<'py> StageClasses<'py> { pub(crate) fn new(py: Python<'py>) -> PyResult { let types = py.import(COMPILE_TYPES_MODULE)?; - let severity = registry::class(types.py(), RecordId::DiagnosticSeverity)?; - let resource = registry::class(py, RecordId::CompiledResourceType)?; - let kinds = registry::class(py, RecordId::SqlReferenceKind)?; + let severity = record_class(types.py(), RecordId::DiagnosticSeverity)?; + let resource = record_class(py, RecordId::CompiledResourceType)?; + let kinds = record_class(py, RecordId::SqlReferenceKind)?; Ok(Self { - 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")?, + diagnostic: record_class(py, RecordId::CompilerDiagnostic)?, + location: record_class(py, RecordId::SourceLocation)?, + model_test_payload: record_class(py, RecordId::CompiledModelSqlTestPayload)?, + compile_phase: enum_member(types.py(), RecordId::DiagnosticPhase, "COMPILE")?, error: native_field(&severity, "ERROR")?, warning: native_field(&severity, "WARNING")?, model_resource: native_field(&resource, "MODEL")?, 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 88c68f1fc8..3e235093fe 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/semantic_shapes/shapes.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/semantic_shapes/shapes.rs @@ -14,6 +14,7 @@ use crate::bindings::_helpers::boundary::keyword_request::Request; use crate::bindings::_helpers::compiled_project::compiled_model_view::{DeclaredRow, InferredRow}; use crate::bindings::_helpers::native_host::adapter::AdapterHost; use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::construct::enum_member; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -78,8 +79,7 @@ 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> = native_field(model, "config")?.getattr("values")?; - let snapshot: Bound<'py, PyAny> = - registry::class(py, RecordId::MaterializationType)?.getattr("SNAPSHOT")?; + let snapshot: Bound<'py, PyAny> = enum_member(py, RecordId::MaterializationType, "SNAPSHOT")?; if values .call_method1("get", ("materialized",))? .eq(snapshot)? @@ -251,8 +251,7 @@ pub(crate) fn project_shapes<'py>( shapes.set_item(format!("{TABLE_FUNCTION_PREFIX}{name}"), shape)?; } } - let enforced: Bound<'py, PyAny> = - registry::class(py, RecordId::ContractPolicy)?.getattr("ENFORCED")?; + let enforced: Bound<'py, PyAny> = enum_member(py, RecordId::ContractPolicy, "ENFORCED")?; let mut expression_names: Vec> = Vec::new(); let mut expressions: Vec> = Vec::new(); for source in native_field(project, "sources")?.try_iter()? { 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 fe32823a87..385c35252b 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 @@ -14,6 +14,7 @@ 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::construct::{enum_from_value, enum_member, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -153,9 +154,8 @@ fn rebound_models<'py>( .getattr("TopologicalSorter")? .call1((graph,))? .call_method0("static_order")?; - let query_class: Object<'py> = registry::class(py, RecordId::NativeColumnQuery)?; - let unknown: Object<'py> = - registry::class(py, RecordId::InferredNullability)?.getattr("UNKNOWN")?; + let query_class: Object<'py> = record_class(py, RecordId::NativeColumnQuery)?; + let unknown: Object<'py> = enum_member(py, RecordId::InferredNullability, "UNKNOWN")?; let infer: Object<'py> = registry::native(py, NativeAttr::InferNativeQueryColumns)?; let mut affected: HashSet = HashSet::new(); let mut models: Vec> = Vec::new(); @@ -301,11 +301,9 @@ fn validated_diagnostics<'py>( expansions.set_item(native_field(&path, "stem")?, expansion)?; } let skipped: Object<'py> = PyString::new(py, BINDING_UNKNOWN_TABLE_INTERNAL_CODE).into_any(); - let diagnostic_class: Object<'py> = registry::class(py, RecordId::CompilerDiagnostic)?; - let severity_class: Object<'py> = registry::class(py, RecordId::DiagnosticSeverity)?; - let phase: Object<'py> = registry::class(py, RecordId::DiagnosticPhase)?.getattr("COMPILE")?; - let model_type: Object<'py> = - registry::class(py, RecordId::CompiledResourceType)?.getattr("MODEL")?; + let diagnostic_class: Object<'py> = record_class(py, RecordId::CompilerDiagnostic)?; + let phase: Object<'py> = enum_member(py, RecordId::DiagnosticPhase, "COMPILE")?; + let model_type: Object<'py> = enum_member(py, RecordId::CompiledResourceType, "MODEL")?; let mut diagnostics: Vec> = Vec::new(); for ((model, request), result) in models.iter().zip(&requests).zip(results.try_iter()?) { let result: Object<'py> = result?; @@ -338,7 +336,11 @@ fn validated_diagnostics<'py>( ("phase", phase.clone()), ( "severity", - severity_class.call1((native_field(&diagnostic, "severity")?,))?, + enum_from_value( + py, + RecordId::DiagnosticSeverity, + native_field(&diagnostic, "severity")?, + )?, ), ("code", native_field(&diagnostic, "code")?), ("message", native_field(&diagnostic, "message")?), 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 1e51b833c9..eeeb21fc15 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 @@ -40,6 +40,7 @@ use crate::bindings::_helpers::dbt_interop::reference_resolver::resolve_external use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_entries::command_plans::build_test_command_plan; +use crate::bindings::_helpers::records::construct::{new_record_with, record_class}; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::result_output::documents::format_test_execution_json; @@ -160,12 +161,12 @@ 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> = registry::class(py, RecordId::NestedProgressChildRow)?; let mut rows: Vec> = Vec::new(); for step in result.getattr("step_results")?.try_iter()? { let step: Bound<'py, PyAny> = step?; - rows.push(call_with( - &row_class, + rows.push(new_record_with( + py, + RecordId::NestedProgressChildRow, &[ ("label", text(py, EXPECT_LABEL)), ( @@ -327,8 +328,9 @@ fn compile_tests<'py>( invocation.getattr("effective_project_dir")?, discovered.clone(), )?; - let options: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::CompilePipelineOptions)?, + let options: Bound<'py, PyAny> = new_record_with( + py, + RecordId::CompilePipelineOptions, &[ ("selected_target", request.getattr("selected_target")?), ("no_sql_validation", request.getattr("no_sql_validation")?), @@ -647,7 +649,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, - ®istry::class(py, RecordId::TestInvocation)?, + &record_class(py, RecordId::TestInvocation)?, !inspect_only, )?; let pipeline: Bound<'_, PyAny> = compile_tests(request, &invocation)?; 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 c7cb9d5ec8..b5e8dd09c8 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 @@ -5,9 +5,9 @@ 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::construct::{enum_member, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; 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, }; @@ -61,13 +61,13 @@ struct Classes<'py> { impl<'py> Classes<'py> { fn new(py: Python<'py>) -> PyResult { Ok(Self { - direct_input: registry::class(py, RecordId::CompileDirectLogicSqlTestInputPayload)?, - resource_type: registry::class(py, RecordId::CompiledResourceType)?, - 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)?, + direct_input: record_class(py, RecordId::CompileDirectLogicSqlTestInputPayload)?, + resource_type: record_class(py, RecordId::CompiledResourceType)?, + phase: enum_member(py, RecordId::DiagnosticPhase, "COMPILE")?, + severity: enum_member(py, RecordId::DiagnosticSeverity, "ERROR")?, + assembly: record_class(py, RecordId::NativeSqlTestAssembly)?, + failure: record_class(py, RecordId::NativeSqlTestFailure)?, + location: record_class(py, RecordId::SourceLocation)?, code: PyString::new(py, super::constants::SQL_TEST_HELPER_REFERENCE_CODE).into_any(), }) } @@ -77,7 +77,7 @@ impl<'py> Classes<'py> { } fn model(&self, name: RecordId) -> PyResult> { - registry::class(self.py(), name) + record_class(self.py(), name) } fn object_key(&self, resource_type: &str, name: &str) -> PyResult> { 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 5245f950ce..0ed8457f9b 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 @@ -16,9 +16,9 @@ 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::construct::{enum_from_value, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; 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, @@ -244,13 +244,13 @@ fn model_cursor<'py>(model: &Bound<'py, PyAny>) -> PyResult> { .extract::() .is_ok_and(|value| !value.is_empty()) }); - let cursor_type = registry::class(py, RecordId::CursorType)?.call1(( + let cursor_type = record_class(py, RecordId::CursorType)?.call1(( declared.unwrap_or_else(|| PyString::new(py, TIMESTAMP_CURSOR_TYPE).into_any()), ))?; let type_name: String = native_field(&cursor_type, "value")?.extract()?; let grain = match config_str(&values, "cursor_grain")? { Some(grain) if type_name == TIMESTAMP_CURSOR_TYPE => { - let grain = registry::class(py, RecordId::CursorGrain)?.call1((grain,))?; + let grain = enum_from_value(py, RecordId::CursorGrain, grain)?; let session = CursorSession::new(py)?; session .grain(&grain)? diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/fixture_validation.rs b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/fixture_validation.rs index 01856fb93e..3fbc5e1b68 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/fixture_validation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/fixture_validation.rs @@ -16,9 +16,9 @@ use crate::bindings::_helpers::planner_strategy::fixture_sql::{ }; use crate::bindings::_helpers::planner_strategy::models::RelationFixturePlanningContext; use crate::bindings::_helpers::planner_strategy::types::FixtureKey; +use crate::bindings::_helpers::records::construct::record_class; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sql_test_glue::constants::FIXTURE_PREFIXES; /// Report every statically provable fixture defect or return the completed fixture SQL groups. @@ -31,7 +31,7 @@ pub(super) fn validated_fixtures<'py>( let py = test.py(); let (chain, [refs, sources, seeds, expected]) = request; let adapter = inputs.1; - let resource_types = registry::class(py, RecordId::CompiledResourceType)?; + let resource_types = record_class(py, RecordId::CompiledResourceType)?; let model = native_field(&resource_types, "MODEL")?; let kinds = [ model.clone(), 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 d2960b60ba..6226495dee 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 @@ -4,12 +4,12 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python, pyfunction}; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; use crate::bindings::_helpers::analysis::model_reads; -use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; use crate::bindings::_helpers::planner_strategy::fixture_context::planning_context; use crate::bindings::_helpers::planner_strategy::models::RelationFixturePlanningContext; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sql_test_glue::models::{SqlTestAssembly, SqlTestPlanOptions}; use crate::bindings::_helpers::sql_test_glue::plan_driver::planned as planned_tests; @@ -94,12 +94,12 @@ pub(crate) fn assemble_sql_test_plans<'py>( let model = model?; models.set_item(model_reads::name_object(&model)?, model)?; } - 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) { - results.push(call_with( - &result_class, + results.push(new_record_with( + py, + RecordId::SqlTestPlanResult, &[ ("entry", py.None().into_bound(py)), ("fixture_diagnostics", diagnostic.clone()), @@ -119,8 +119,9 @@ pub(crate) fn assemble_sql_test_plans<'py>( context.as_ref().map(Bound::get), )?); if !errors.is_empty() { - results.push(call_with( - &result_class, + results.push(new_record_with( + py, + RecordId::SqlTestPlanResult, &[ ("entry", py.None().into_bound(py)), ("fixture_diagnostics", PyTuple::new(py, errors)?.into_any()), @@ -128,8 +129,9 @@ pub(crate) fn assemble_sql_test_plans<'py>( )?); continue; } - results.push(call_with( - &result_class, + results.push(new_record_with( + py, + RecordId::SqlTestPlanResult, &[ ( "entry", 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 38ee42ab5c..ed5d036a6f 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 @@ -1,11 +1,10 @@ //! Host SQL-test request preparation, native planning, and Python result conversion. use crate::bindings::_helpers::compile_driver::compile_timings::add_compile_timing; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported}; use crate::bindings::_helpers::planner_phases::objects::object; use crate::bindings::_helpers::planner_strategy::fixture_context::count_chain_resolution; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -130,8 +129,9 @@ pub(crate) fn planned<'py>( .call1(("native SQL-test planning omitted rendered SQL",))?, )); } - plans.push(call_with( - ®istry::class(py, RecordId::NativeSqlTestArtifact)?, + plans.push(new_record_with( + py, + RecordId::NativeSqlTestArtifact, &[ ("sql", sql), ("model_names", native_field(&plan, "model_names")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_entries.rs b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_entries.rs index b59b1d4c69..26fd60e004 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_entries.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_entries.rs @@ -5,13 +5,13 @@ use pyo3::types::{PyDict, PyDictMethods, PyListMethods, PyTuple}; use crate::bindings::_helpers::planner_strategy::constants::MODEL_REFERENCE_KIND; use crate::bindings::_helpers::planner_strategy::models::RelationFixturePlanningContext; +use crate::bindings::_helpers::records::construct::{new_record, record_class}; use crate::bindings::_helpers::sql_test_glue::constants::{TABLE_FN, UDF}; use crate::bindings::_helpers::sql_test_glue::plan_values::model_payload; use sqlbuild_analysis::compiler::main::missing_expected_columns::missing_expected_columns; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sql_test_glue::authored_ctes::prefixed_ctes; fn prefixed_names<'py>(test: &Bound<'py, PyAny>, prefix: &str) -> PyResult> { @@ -134,7 +134,7 @@ pub(super) fn entry<'py>( fields.set_item("chain", native_field(plan, "chain")?)?; fields.set_item("assertions", native_field(plan, "assertions")?)?; fields.set_item("sql_analysis_enabled", analysis)?; - registry::class(py, RecordId::SqlTestPlanEntry)?.call((), Some(&fields)) + new_record(py, RecordId::SqlTestPlanEntry, (), Some(&fields)) } pub(super) fn expected_diagnostics( @@ -147,7 +147,7 @@ pub(super) fn expected_diagnostics( return Ok(result); }; let py = test.py(); - let resource_types = registry::class(py, RecordId::CompiledResourceType)?; + let resource_types = record_class(py, RecordId::CompiledResourceType)?; for step in native_field(plan, "chain")?.try_iter()? { let step = step?; let name = native_field(&step, "model_name")?; 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 04b327563a..e7bf07b7db 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,6 +1,7 @@ //! Project native planning failures into existing located diagnostic exceptions. use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::records::construct::new_record; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -33,7 +34,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)?; - registry::class(py, RecordId::SourceLocation)?.call((), Some(&arguments)) + new_record(py, RecordId::SourceLocation, (), Some(&arguments)) } pub(super) fn planning_error( 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 a4791201f0..51dac5c47a 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,17 +1,17 @@ //! Materialize native plan rows as the existing executor-facing dataclasses. -use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::construct::{enum_from_value, new_record_with}; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sql_test_glue::planning::{AssertionRow, ChainRow, PlanRow}; use pyo3::IntoPyObjectExt; -use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; +use pyo3::prelude::{Bound, PyAny, PyResult, Python}; use pyo3::types::PyTuple; fn chain_step<'py>(py: Python<'py>, row: ChainRow) -> PyResult> { let (model, sql, expected_sql, lifted, body, expected_columns, expected_lifted) = row; - call_with( - ®istry::class(py, RecordId::ChainStep)?, + new_record_with( + py, + RecordId::ChainStep, &[ ("model_name", model.into_bound_py_any(py)?), ("resolved_sql", sql.into_bound_py_any(py)?), @@ -35,8 +35,9 @@ fn chain_step<'py>(py: Python<'py>, row: ChainRow) -> PyResult fn assertion_step<'py>(py: Python<'py>, row: AssertionRow) -> PyResult> { let (name, sql, lifted, body) = row; - call_with( - ®istry::class(py, RecordId::SqlTestAssertionStep)?, + new_record_with( + py, + RecordId::SqlTestAssertionStep, &[ ("name", name.into_bound_py_any(py)?), ("resolved_sql", sql.into_bound_py_any(py)?), @@ -57,19 +58,23 @@ pub(super) fn plan(py: Python<'_>, row: PlanRow) -> PyResult> { assertions.push(assertion_step(py, value)?); } let mut warnings: Vec> = Vec::new(); - let severity = registry::class(py, RecordId::WarningSeverity)?; for (model_name, level, message) in warning_rows { - warnings.push(call_with( - ®istry::class(py, RecordId::PlanWarning)?, + warnings.push(new_record_with( + py, + RecordId::PlanWarning, &[ ("model_name", model_name.into_bound_py_any(py)?), - ("severity", severity.call1((level,))?), + ( + "severity", + enum_from_value(py, RecordId::WarningSeverity, level)?, + ), ("message", message.into_bound_py_any(py)?), ], )?); } - call_with( - ®istry::class(py, RecordId::NativeSqlTestPlan)?, + new_record_with( + 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 06cca996b6..54dbc3b4d6 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,16 +4,16 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::PyString; use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::records::construct::record_class; use crate::bindings::_helpers::records::fields::field as native_field; 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 { - native_field(test, "payload")?.is_instance(®istry::class( + native_field(test, "payload")?.is_instance(&record_class( test.py(), 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 6a7f5ed88e..1be9e9d02e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sqltext/authored_values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sqltext/authored_values.rs @@ -1,7 +1,7 @@ //! Authored header values as the Python header objects: scalars, sets, constants and hooks. +use crate::bindings::_helpers::records::construct::new_record; 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; @@ -29,9 +29,7 @@ fn instance( class: RecordId, arguments: &pyo3::Bound<'_, PyDict>, ) -> PyResult> { - Ok(registry::class(py, class)? - .call((), Some(arguments))? - .unbind()) + Ok(new_record(py, class, (), Some(arguments))?.unbind()) } fn values_tuple( 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 744ebb6b0b..22b2ff067d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sqltext/sql_references.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sqltext/sql_references.rs @@ -12,7 +12,9 @@ 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; +use crate::bindings::_helpers::records::construct::{ + enum_from_value, enum_member, new_record_with, +}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -109,17 +111,18 @@ fn scanned<'py>( return Err(PyErr::from_value(error)); } }; - 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() .map(|reference| { - call_with( - &reference_type, + new_record_with( + py, + RecordId::CompileSqlReference, &[ - ("ref_kind", kind_type.call1((reference.kind,))?), + ( + "ref_kind", + enum_from_value(py, RecordId::SqlReferenceKind, reference.kind)?, + ), ("ref_name", text(py, reference.name)?), ("ref_package", text(py, reference.package)?), ( @@ -134,10 +137,14 @@ fn scanned<'py>( .invalid_calls .into_iter() .map(|call| { - call_with( - &invalid_type, + new_record_with( + py, + RecordId::InvalidSqlReferenceCall, &[ - ("ref_kind", kind_type.call1((call.kind,))?), + ( + "ref_kind", + enum_from_value(py, RecordId::SqlReferenceKind, call.kind)?, + ), ("call", text(py, call.call)?), ("start", text(py, call.start)?), ("message", text(py, call.message)?), @@ -159,8 +166,9 @@ fn scan_sql_reference_calls<'py>( ) -> PyResult> { let py: Python<'py> = syntax.py(); let (references, invalid_calls) = scanned(py, sql, syntax)?; - call_with( - ®istry::class(py, RecordId::SqlReferenceScan)?, + new_record_with( + py, + RecordId::SqlReferenceScan, &[ ("references", PyTuple::new(py, references)?.into_any()), ("invalid_calls", PyTuple::new(py, invalid_calls)?.into_any()), @@ -178,18 +186,18 @@ pub(crate) fn extract_references<'py>( let py: Python<'py> = syntax.py(); let (references, invalid_calls) = scanned(py, sql, syntax)?; let report: Bound<'py, PyAny> = registry::native(py, NativeAttr::ReportCompileDiagnostic)?; - let diagnostic_type: Bound<'py, PyAny> = registry::class(py, RecordId::CompilerDiagnostic)?; for call in invalid_calls { - let diagnostic: Bound<'py, PyAny> = call_with( - &diagnostic_type, + let diagnostic: Bound<'py, PyAny> = new_record_with( + py, + RecordId::CompilerDiagnostic, &[ ( "phase", - registry::class(py, RecordId::DiagnosticPhase)?.getattr("COMPILE")?, + enum_member(py, RecordId::DiagnosticPhase, "COMPILE")?, ), ( "severity", - registry::class(py, RecordId::DiagnosticSeverity)?.getattr("ERROR")?, + enum_member(py, RecordId::DiagnosticSeverity, "ERROR")?, ), ("code", text(py, REFERENCE_CALL_SYNTAX_CODE)?), ("message", native_field(&call, "message")?), 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 372caafccd..225750664c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/state_rows/freshness.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/state_rows/freshness.rs @@ -19,6 +19,7 @@ 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::construct::{enum_member, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -213,7 +214,7 @@ pub(crate) fn age_policy<'py>( if policy.is_none() { return Ok(None); } - let statuses: Bound<'py, PyAny> = registry::class(py, RecordId::SourceFreshnessAgeStatus)?; + let statuses: Bound<'py, PyAny> = record_class(py, RecordId::SourceFreshnessAgeStatus)?; if !date_time_typed(data_version)? { return native_field(&statuses, "UNKNOWN").map(Some); } @@ -232,7 +233,7 @@ pub(crate) fn age_policy<'py>( AgeStatus::Warn => "WARN", AgeStatus::Error => "ERROR", }; - statuses.getattr(member).map(Some) + enum_member(py, RecordId::SourceFreshnessAgeStatus, member).map(Some) } pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { 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 d62f930987..4de546921b 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,9 @@ use sqlbuild_state::events::models::{ ColumnEventKey, EventOrder, EventRelation, MoveEventKey, OldNameFactKey, OldNameStep, }; -use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; 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; @@ -111,13 +110,13 @@ pub(crate) fn old_name_views<'py>( }) }) .collect::>()?; - 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() .map(|indexes| { - call_with( - &history, + new_record_with( + py, + RecordId::OldNameViewHistory, &[ ("move", moves[indexes.moved].clone()), ("required", facts[indexes.required].clone()), 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 7576ecfba1..0805e5d79b 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 @@ -16,11 +16,11 @@ use sqlbuild_state::events::main::grants_json::grants_json; 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::compile_driver::python_calls::imported; use crate::bindings::_helpers::cursor::values::DATETIME_MODULE; +use crate::bindings::_helpers::records::construct::{enum_member, new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; 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, @@ -41,7 +41,6 @@ fn create_sql<'py>(request: Option<&Bound<'py, PyDict>>) -> PyResult = 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) { @@ -49,7 +48,10 @@ fn create_sql<'py>(request: Option<&Bound<'py, PyDict>>) -> PyResult = PyTuple::new(py, layout.columns)?.into_any(); let sql: String = state_table_create_sql( @@ -222,8 +224,9 @@ impl<'py> StoredRow<'_, 'py> { } fn relation(&self, py: Python<'py>, prefix: &str) -> PyResult> { - call_with( - ®istry::class(py, RecordId::MigrationRelation)?, + new_record_with( + py, + RecordId::MigrationRelation, &[ ( "database", @@ -257,7 +260,7 @@ fn enumerated<'py>( class: RecordId, value: Bound<'py, PyAny>, ) -> PyResult> { - registry::class(py, class)?.call1((value,)) + new_record(py, class, (value,), None) } fn known_member<'py>( @@ -280,8 +283,9 @@ 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> { - call_with( - ®istry::class(py, RecordId::ColumnMigrationEvent)?, + new_record_with( + py, + RecordId::ColumnMigrationEvent, &[ ("event_id", row.text("event_id")?), ("target_name", row.nullable(py, "target_name")?), @@ -379,7 +384,7 @@ fn old_name_event<'py>( ("grants_copied", grants), ("view_sql", row.nullable(py, "view_sql")?), ]; - call_with(®istry::class(py, RecordId::OldNameViewEvent)?, &fields).map(Some) + new_record_with(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/type_system/nullability.rs b/crates/sqlbuild-python/src/bindings/_helpers/type_system/nullability.rs index 7a48d910e9..3d72270e91 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/type_system/nullability.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/type_system/nullability.rs @@ -6,14 +6,14 @@ use pyo3::{pyfunction, wrap_pyfunction}; use sqlbuild_analysis::nullability_rules::main::conditional_result_nullability::conditional_result_nullability; use sqlbuild_analysis::nullability_rules::main::safe_cast_nullability::safe_cast_nullability; +use crate::bindings::_helpers::records::construct::{enum_from_value, enum_member}; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; const UNKNOWN_MEMBER: &str = "UNKNOWN"; /// The `InferredNullability` member whose value is `value`. fn member<'py>(args: &Bound<'py, PyAny>, value: &str) -> PyResult> { - registry::class(args.py(), RecordId::InferredNullability)?.call1((value,)) + enum_from_value(args.py(), RecordId::InferredNullability, value) } /// The text of one argument; an argument that is not a string matches no state. @@ -35,7 +35,7 @@ fn argument_values(args: &Bound<'_, PyAny>, count: usize) -> PyResult(args: &Bound<'py, PyAny>) -> PyResult> { if !args.is_truthy()? { - return registry::class(args.py(), RecordId::InferredNullability)?.getattr(UNKNOWN_MEMBER); + return enum_member(args.py(), RecordId::InferredNullability, UNKNOWN_MEMBER); } args.get_item(0) } 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 a3b11eb88b..88558274b3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/workspace_commands/commands.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/workspace_commands/commands.rs @@ -23,6 +23,7 @@ use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, 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::construct::new_record; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -120,7 +121,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)?)?; - registry::class(py, RecordId::SkillUpdateResult)?.call((), Some(&keywords)) + new_record(py, RecordId::SkillUpdateResult, (), Some(&keywords)) } /// Check or refresh generated project skills without changing command success. @@ -131,7 +132,7 @@ fn maintain_sqlbuild_skills(py: Python<'_>, project_dir: PathBuf) -> PyResult = PyDict::new(py); keywords.set_item("message", message)?; - registry::class(py, RecordId::SkillMaintenanceResult)?.call((), Some(&keywords)) + new_record(py, RecordId::SkillMaintenanceResult, (), Some(&keywords)) } /// Return every packaged skill file keyed by skill-relative path, with generated markers. From d2f5c9774d536cce204558cd09b7486957ab36e7 Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Sat, 17 Oct 2026 02:31:23 +0100 Subject: [PATCH 06/28] refactor: call native helpers directly from refactor, render, rules and semantic bindings The native analysis seam e2e test no longer spies on infer_native_expression_source_shapes; the stage-work counter it already checks covers that stage. --- .../_helpers/analysis/normalization.rs | 2 +- .../analysis_session/stage_functions.rs | 4 ++-- .../bindings/_helpers/discovery/guidance.rs | 2 +- .../_helpers/refactor/contract_compare.rs | 13 ++++------- .../_helpers/refactor/python_strings.rs | 2 +- .../_helpers/refactor/refactor_facts.rs | 23 ++++++------------- .../_helpers/render/macro_expansion_result.rs | 10 +++----- .../src/bindings/_helpers/rules/catalogue.rs | 13 ++++++----- .../_helpers/rules/custom_evaluation.rs | 9 +++----- .../bindings/_helpers/rules/custom_hosts.rs | 6 ++--- .../src/bindings/_helpers/rules/rule_plan.rs | 17 +++++++------- .../_helpers/semantic_checks/inputs.rs | 7 +++--- .../_helpers/semantic_shapes/shapes.rs | 10 ++++---- .../cli/commands/main/compile/helpers.py | 1 - .../compile/test_native_analysis_seams.py | 2 -- 15 files changed, 49 insertions(+), 72 deletions(-) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/analysis/normalization.rs b/crates/sqlbuild-python/src/bindings/_helpers/analysis/normalization.rs index d5f3dda1be..396c065d76 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/analysis/normalization.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/analysis/normalization.rs @@ -8,7 +8,7 @@ use crate::bindings::_helpers::boundary::panics::value_error; use crate::bindings::types::CompilerDetach; #[pyfunction] -fn normalize_analysis_sql( +pub(crate) fn normalize_analysis_sql( py: Python<'_>, request: crate::bindings::models::NormalizationInput, ) -> PyResult { 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 0ca962990d..4ded9cda1a 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 @@ -59,7 +59,7 @@ fn infer_native_query_column_rows<'py>( /// Each query's columns as `NativeQueryColumns`; a native internal failure raises. #[pyfunction] #[pyo3(signature = (**request))] -fn infer_native_query_columns<'py>( +pub(crate) fn infer_native_query_columns<'py>( request: Option<&Bound<'py, PyDict>>, ) -> PyResult> { let request = Request::new("infer_native_query_columns", request)?; @@ -114,7 +114,7 @@ fn infer_native_query_columns<'py>( /// One shape per expression, cached on the profile's binding catalog; a failure raises. #[pyfunction] #[pyo3(signature = (**request))] -fn infer_native_expression_source_shapes<'py>( +pub(crate) fn infer_native_expression_source_shapes<'py>( request: Option<&Bound<'py, PyDict>>, ) -> PyResult> { let request = Request::new("infer_native_expression_source_shapes", request)?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/guidance.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/guidance.rs index 7343bc751e..4d95ed7b3e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/guidance.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/guidance.rs @@ -55,7 +55,7 @@ pub(crate) fn microbatch_guidance() -> (String, String) { #[pyfunction] #[pyo3(signature = (*, allowed))] -fn explicit_references_help(allowed: &str) -> String { +pub(crate) fn explicit_references_help(allowed: &str) -> String { references_help(allowed) } 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 9bb465ab9c..fb6e1d5241 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/refactor/contract_compare.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/refactor/contract_compare.rs @@ -7,12 +7,12 @@ 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::contracts::spec_resolution::matching_dynamic_families; use crate::bindings::_helpers::native_host::adapter::AdapterHost; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::records::construct::{enum_from_value, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::type_system::equality::types_equal as native_types_equal; const DATACLASSES_MODULE: &str = "dataclasses"; @@ -253,13 +253,8 @@ fn undeclared_finding<'py>( let py: Python<'py> = vocabulary.py(); let name: String = native_field(physical, "name")?.extract()?; let physical_type: Bound<'py, PyAny> = native_field(physical, "type")?; - let matching: Bound<'py, PyAny> = keyword_call( - ®istry::native(py, NativeAttr::MatchingDynamicFamilies)?, - vec![ - ("families", native_field(item, "dynamic_columns")?), - ("column_name", text(py, &name)), - ], - )?; + let matching: Bound<'py, PyAny> = + matching_dynamic_families(&native_field(item, "dynamic_columns")?, &name)?.into_any(); if matching.len()? == 1 { let family: Bound<'py, PyAny> = matching.get_item(0)?; let family_type: Bound<'py, PyAny> = native_field(&family, "type")?; 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 618efe2ca4..6da26b86e8 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/refactor/python_strings.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/refactor/python_strings.rs @@ -58,7 +58,7 @@ fn read_text(path: PathBuf) -> Option { /// Flag Python strings that name a model or column; with context, only SQL naming it too. #[pyfunction] #[pyo3(signature = (**request))] -fn find_python_string_locations<'py>( +pub(crate) fn find_python_string_locations<'py>( request: Option<&Bound<'py, PyDict>>, ) -> PyResult> { let request = Request::new("find_python_string_locations", request)?; 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 9969029211..bb51e1dc26 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/refactor/refactor_facts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/refactor/refactor_facts.rs @@ -11,10 +11,11 @@ use sqlbuild_refactor::refactoring::models::{ SchemaColumnFacts, SqlFileRole, }; +use crate::bindings::_helpers::model_config::config_values::config_str; use crate::bindings::_helpers::records::construct::{enum_member, new_record}; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::refactor::python_strings::find_python_string_locations; 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; @@ -28,10 +29,6 @@ fn posix(path: &Bound<'_, PyAny>) -> PyResult { path.call_method0("as_posix")?.extract() } -fn native<'py>(py: Python<'py>, name: NativeAttr) -> PyResult> { - registry::native(py, name) -} - fn discovered_files(files: &Bound<'_, PyAny>) -> PyResult> { let mut discovered: Vec = Vec::new(); for item in files.try_iter()? { @@ -123,12 +120,9 @@ fn model(model: &Bound<'_, PyAny>, expansion: Option<&Bound<'_, PyAny>>) -> PyRe "update", (native_field(model, "config")?.getattr("values")?,), )?; - let options: Bound<'_, PyDict> = PyDict::new(py); - options.set_item("values", &values)?; - options.set_item("key", MATERIALIZED_KEY)?; - let materialized: Option = registry::native(py, NativeAttr::GetConfigStr)? - .call((), Some(&options))? - .extract()?; + let materialized: Option = config_str(&values, MATERIALIZED_KEY)? + .map(|value| value.extract()) + .transpose()?; let migrate_from: Option> = values.get_item(MIGRATE_FROM_KEY)?; let mut deps: Vec = Vec::new(); for dep in native_field(model, "deps")?.try_iter()? { @@ -267,10 +261,7 @@ fn python_locations( )?; } let mut locations: Vec = Vec::new(); - for location in native(py, NativeAttr::FindPythonStringLocations)? - .call((), Some(&options))? - .try_iter()? - { + for location in find_python_string_locations(Some(&options))?.try_iter()? { let location: Bound<'_, PyAny> = location?; locations.push(ManualLocation { path: native_field(&location, "path")?.extract()?, 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 5c2ac68218..ba98c8c033 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,7 @@ use pyo3::{pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, imported, keywords, }; +use crate::bindings::_helpers::discovery::guidance::explicit_references_help; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::native_host::user_code::UserCodeHost; use crate::bindings::_helpers::records::construct::{ @@ -57,13 +58,8 @@ pub(crate) struct ExpansionRequest<'py> { } fn generated_classes(py: Python<'_>) -> PyResult> { - let help: Bound<'_, PyAny> = registry::native(py, NativeAttr::ExplicitReferencesHelp)?.call( - (), - Some(&keywords( - py, - &[("allowed", PyString::new(py, GENERATED_ALLOWED).into_any())], - )?), - )?; + let help: Bound<'_, PyAny> = + PyString::new(py, &explicit_references_help(GENERATED_ALLOWED)).into_any(); let markers: Bound<'_, PyAny> = PyList::new(py, GENERATED_REFERENCE_MARKERS)?.into_any(); keywords( py, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/catalogue.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/catalogue.rs index bb581b4ea5..955823e09e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/catalogue.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/catalogue.rs @@ -16,9 +16,8 @@ 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::planner_scope::scope::build_planner_scope; use crate::bindings::_helpers::records::fields::{field as record_field, path_posix, replaced}; -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::config_json::asdict_json; use crate::bindings::_helpers::rules::custom_rule_evidence::verify_custom_rules as verify_rule_sources; @@ -355,7 +354,6 @@ pub(crate) fn resolved_ignore_selectors<'py>( if !scoped { return Ok(config.clone()); } - let scope: Bound<'py, PyAny> = registry::native(py, NativeAttr::BuildPlannerScope)?; let path_by_key: HashMap<(String, String), String> = resource_paths(project)?; let mut resolved: Vec> = Vec::with_capacity(ignores.len()); for ignore in ignores { @@ -364,9 +362,12 @@ pub(crate) fn resolved_ignore_selectors<'py>( resolved.push(ignore); continue; } - let selected: Bound<'py, PyAny> = call_with( - &scope, - &[("project", project.clone()), ("select", selectors.clone())], + let selected: Bound<'py, PyAny> = build_planner_scope( + py, + Some(keywords( + py, + &[("project", project.clone()), ("select", selectors.clone())], + )?), )?; let mut selector_paths: BTreeSet = BTreeSet::new(); for key in record_field(&selected, "selected_keys")?.try_iter()? { 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 e8a10d384a..40d38c9325 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_evaluation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_evaluation.rs @@ -18,10 +18,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::imported; use crate::bindings::_helpers::compile_driver::python_calls::keywords; +use crate::bindings::_helpers::compiled_project::records::compiled_project_record; use crate::bindings::_helpers::records::construct::{new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::rules::constants::{ CUSTOM_HOST_RUNTIME_VERSION, CUSTOM_RULES_CACHE_FILE, }; @@ -306,10 +306,7 @@ fn orjson_key(key: &FactKey) -> String { /// The project without state no rule fact reads, before digests and host payloads use it. 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> = registry::native(py, NativeAttr::CompiledProjectRecord)? - .call((project,), Some(&options))?; + let record: Bound<'py, PyAny> = compiled_project_record(project, false)?; let changes: Bound<'py, PyDict> = PyDict::new(py); for (name, cleared) in HOST_PROJECT_CLEARED { match cleared { 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 9ddb7ad7c1..78a7b839af 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_hosts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_hosts.rs @@ -26,10 +26,11 @@ 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::compiled_project::records::compiled_project_record; use crate::bindings::_helpers::native_host::constants::CUSTOM_HOST_LAUNCH_MODULE; use crate::bindings::_helpers::records::construct::new_record; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::constants::{ CUSTOM_HOST_ABANDONED_SUFFIX, CUSTOM_HOST_CANCELLED_MARKER, CUSTOM_HOST_RUNTIME_VERSION, @@ -421,8 +422,7 @@ pub(crate) fn host_payload_project<'py>( } models.push(new_record(py, RecordId::CompiledModel, (), Some(&fields))?); } - let record: Bound<'py, PyAny> = - registry::native(py, NativeAttr::CompiledProjectRecord)?.call1((project,))?; + let record: Bound<'py, PyAny> = compiled_project_record(project, true)?; let options: Bound<'py, PyDict> = PyDict::new(py); options.set_item("models", PyTuple::new(py, models)?)?; options.set_item("native_project", py.None())?; 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 28af3ea949..60127a6820 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_plan.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_plan.rs @@ -8,12 +8,13 @@ use std::path::Path; use crate::bindings::_helpers::boundary::keyword_request::Request; use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::compile_driver::python_calls::keywords; +use crate::bindings::_helpers::compiled_project::records::project_with; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::native_host::user_code::UserCodeHost; use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::{field, replaced}; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::rules::catalogue::{ references_custom_rules, resolved_ignore_selectors, rule_catalogue, select_rules, verify_custom_rules, @@ -158,12 +159,12 @@ fn selected_models_project<'py>( models.push(entry.model); } } - call_with( - ®istry::native(py, NativeAttr::ProjectWith)?, - &[ - ("project", project.clone()), - ("models", PyTuple::new(py, models)?.into_any()), - ], + project_with( + project, + Some(keywords( + py, + &[("models", PyTuple::new(py, models)?.into_any())], + )?), ) } 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 62f6231ee8..c038154c8d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/inputs.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/inputs.rs @@ -7,10 +7,10 @@ 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::analysis_session::stage_functions::infer_native_query_columns; use crate::bindings::_helpers::records::construct::new_record; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::ids::RecordId; 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; @@ -74,8 +74,7 @@ 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> = - registry::native(py, NativeAttr::InferNativeQueryColumns)?.call((), Some(&arguments))?; + let analyses: Bound<'py, PyAny> = infer_native_query_columns(Some(&arguments))?.into_any(); 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_shapes/shapes.rs b/crates/sqlbuild-python/src/bindings/_helpers/semantic_shapes/shapes.rs index 3e235093fe..ce6cb0467e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/semantic_shapes/shapes.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/semantic_shapes/shapes.rs @@ -10,14 +10,15 @@ use crate::bindings::_helpers::analysis::binding_catalog::{ use crate::bindings::_helpers::analysis::model_reads::{ declared_rows, dynamic_columns_declared, inferred_rows, star_flags, unchecked_columns, }; +use crate::bindings::_helpers::analysis::normalization::normalize_analysis_sql; +use crate::bindings::_helpers::analysis_session::stage_functions::infer_native_expression_source_shapes; use crate::bindings::_helpers::boundary::keyword_request::Request; use crate::bindings::_helpers::compiled_project::compiled_model_view::{DeclaredRow, InferredRow}; use crate::bindings::_helpers::native_host::adapter::AdapterHost; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::records::construct::enum_member; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::ids::RecordId; const DATACLASSES_MODULE: &str = "dataclasses"; const UNKNOWN: &str = "UNKNOWN"; @@ -138,7 +139,7 @@ fn binding_shape<'py>( request.set_item("stubs", PyDict::new(py))?; request.set_item("placeholders", PyDict::new(py))?; let sql: Bound<'py, PyAny> = - registry::native(py, NativeAttr::NormalizeAnalysisSql)?.call1((request,))?; + PyString::new(py, &normalize_analysis_sql(py, request.extract()?)?).into_any(); let arguments: Bound<'py, PyDict> = PyDict::new(py); arguments.set_item("sql", sql)?; arguments.set_item("columns", columns)?; @@ -274,8 +275,7 @@ pub(crate) fn project_shapes<'py>( arguments.set_item("expressions", PyTuple::new(py, expressions)?)?; arguments.set_item("profile", &profile)?; let source_shapes: Bound<'py, PyAny> = - registry::native(py, NativeAttr::InferNativeExpressionSourceShapes)? - .call((), Some(&arguments))?; + infer_native_expression_source_shapes(Some(&arguments))?.into_any(); 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/tests/e2e/src/sqlbuild/cli/commands/main/compile/helpers.py b/tests/e2e/src/sqlbuild/cli/commands/main/compile/helpers.py index 71b02a24a6..f56f722feb 100644 --- a/tests/e2e/src/sqlbuild/cli/commands/main/compile/helpers.py +++ b/tests/e2e/src/sqlbuild/cli/commands/main/compile/helpers.py @@ -4805,7 +4805,6 @@ def full_macro_call_runs( _ANALYSIS_SEAMS: tuple[tuple[ModuleType, str], ...] = ( - (native_module, "infer_native_expression_source_shapes"), (plan_artifacts_module, "plan_compiled_sql_test_artifacts"), ) diff --git a/tests/e2e/src/sqlbuild/cli/commands/main/compile/test_native_analysis_seams.py b/tests/e2e/src/sqlbuild/cli/commands/main/compile/test_native_analysis_seams.py index 64e5273d6b..331521fd5d 100644 --- a/tests/e2e/src/sqlbuild/cli/commands/main/compile/test_native_analysis_seams.py +++ b/tests/e2e/src/sqlbuild/cli/commands/main/compile/test_native_analysis_seams.py @@ -26,7 +26,6 @@ NativeAnalysisSeamTestCase( description="models_contracts_lineage_and_sql_tests", expected_native_returns={ - "infer_native_expression_source_shapes": [ANY, ANY, ANY], "plan_compiled_sql_test_artifacts": [ANY], }, expected_stage_work={ @@ -62,7 +61,6 @@ def test_given_project_when_compiling_then_native_seams_answer( stage: stage_work.get(stage, 0) for stage in test_case.expected_stage_work } == test_case.expected_stage_work for seams in (native_seams,): - assert isinstance(seams["infer_native_expression_source_shapes"][0], tuple) planned_artifacts: object = seams["plan_compiled_sql_test_artifacts"][0] assert isinstance(planned_artifacts, tuple) assert planned_artifacts From c9518867810c229bff9181b372035ea0ff997d1d Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Sat, 17 Oct 2026 03:10:43 +0100 Subject: [PATCH 07/28] refactor: call schema validation, query reanalysis and rules request helpers directly Semantic completion, source rebinding and native rules evaluation now call the Rust functions instead of sqlbuild._native attributes. Tests that replaced those attributes now read new stage-work counters (schema_revalidations, source_rebinding_analyses, rules_memo_reuses) or build the rules request directly; the request's facts JSON now includes its scope index. --- .../src/bindings/_helpers/rules/constants.rs | 3 + .../src/bindings/_helpers/rules/rows.rs | 24 +++---- .../bindings/_helpers/rules/rule_dispatch.rs | 65 ++++++++++--------- .../bindings/_helpers/scopes/scope_reports.rs | 2 +- .../_helpers/semantic_checks/completion.rs | 10 ++- .../_helpers/semantic_checks/constants.rs | 4 ++ .../bindings/_helpers/semantic_checks/mod.rs | 1 + .../_helpers/semantic_checks/stage_objects.rs | 36 ++++------ .../_helpers/semantic_checks/type_recovery.rs | 26 ++++---- .../_helpers/semantic_shapes/constants.rs | 4 ++ .../bindings/_helpers/semantic_shapes/mod.rs | 1 + .../semantic_shapes/source_bindings.rs | 12 ++-- .../src/sqlbuild/cli/commands/main/helpers.py | 21 +----- .../commands/main/test_source_rebinding.py | 8 +-- .../compiler/pipeline/test_semantic_triage.py | 25 +++---- .../rule_engine/_helpers/engine/helpers.py | 63 +++++++----------- .../_helpers/engine/test_native_facts.py | 1 - .../_helpers/engine/test_scope_facts.py | 8 +-- .../rule_engine/_helpers/run/helpers.py | 19 +----- .../_helpers/run/test_native_rows.py | 8 ++- 20 files changed, 148 insertions(+), 193 deletions(-) create mode 100644 crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/constants.rs create mode 100644 crates/sqlbuild-python/src/bindings/_helpers/semantic_shapes/constants.rs diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/constants.rs index 5ff1bb5822..81f80b8efa 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/constants.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/constants.rs @@ -26,3 +26,6 @@ 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-v3"; + +/// Native rules evaluations answered from the response memo. +pub(crate) const RULES_MEMO_REUSES: &str = "rules_memo_reuses"; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/rows.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/rows.rs index 63acdfc643..7ab29e953d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/rows.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/rows.rs @@ -28,7 +28,7 @@ use sqlbuild_rules::rule_cache::main::rules_bulk_cache_path::rules_bulk_cache_pa use crate::bindings::_helpers::boundary::keyword_request::Request; use crate::bindings::_helpers::boundary::panics::{compiler_guard, value_error}; 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::compile_driver::python_calls::imported; use crate::bindings::_helpers::compiled_project::audit_views::NativeCompiledAudit; use crate::bindings::_helpers::compiled_project::project::NativeCompiledProject; use crate::bindings::_helpers::compiled_project::records::compiled_project_facts; @@ -36,13 +36,13 @@ use crate::bindings::_helpers::compiled_project::records::declaration_rows; use crate::bindings::_helpers::compiled_project::values::plain_value; use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::rules::config_json::asdict_json; use crate::bindings::_helpers::rules::fact_replay::{project_attribute, project_models}; 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::scopes::scope_reports::metadata_projection; use crate::bindings::_helpers::type_system::equality::types_equal; use crate::bindings::types::CompilerDetach; @@ -75,13 +75,13 @@ struct ProjectRows<'py> { /// A natively built rules request held until it is evaluated once. #[pyclass(module = "sqlbuild._native")] -struct NativeRulesRequest { +pub(crate) struct NativeRulesRequest { request: Mutex>, } #[pymethods] impl NativeRulesRequest { - /// The SQL test and scenario facts this request carries, as `{"sql_tests", "sql_scenarios"}` JSON. + /// The SQL test, scenario and scope facts this request carries, as `{"sql_tests", "sql_scenarios", "scope_index"}` JSON. fn sql_facts_json(&self) -> PyResult { let guard = self .request @@ -93,6 +93,7 @@ impl NativeRulesRequest { serde_json::to_string(&serde_json::json!({ "sql_tests": rows.request.sql_tests, "sql_scenarios": rows.request.sql_scenarios, + "scope_index": rows.request.scope_index, })) .map_err(value_error) } @@ -101,7 +102,9 @@ impl NativeRulesRequest { /// Build the built-in rules request natively from the compiled project's rows. #[pyfunction] #[pyo3(signature = (**options))] -fn build_rules_request(options: Option<&Bound<'_, PyDict>>) -> PyResult { +pub(crate) fn build_rules_request( + options: Option<&Bound<'_, PyDict>>, +) -> PyResult { let request = Request::new("build_rules_request", options)?; let py: Python<'_> = request.py(); as_rules_error(py, built_request(&request)) @@ -120,10 +123,9 @@ fn built_request(options: &Request<'_, '_>) -> PyResult { "project_dir": resolved.str()?.to_str()?, "dialect": dialect.extract::()?, "config": config_value(&config)?, - "scope_index": plain_value(&call_with( - ®istry::native(py, NativeAttr::ScopeMetadataProjection)?, - &[("index", project_attribute(&project, "scope_index")?)], - )?)?, + "scope_index": plain_value( + metadata_projection(py, &project_attribute(&project, "scope_index")?)?.bind(py), + )?, "defer_suppressions": true, "custom_rules": plain_value(&options.get("custom_payloads")?)?, "rules_cache_path": path_text(py, rules_bulk_cache_path(&resolved_path, NATIVE_RULES_CACHE_FILE))?, @@ -219,7 +221,7 @@ fn built_request(options: &Request<'_, '_>) -> PyResult { /// Answer the request from the response memo or evaluate it, decoding findings natively. #[pyfunction] -fn evaluate_rules_request<'py>( +pub(crate) fn evaluate_rules_request<'py>( py: Python<'py>, request: &Bound<'py, NativeRulesRequest>, ) -> PyResult> { 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 cecaca1e14..aa7b557440 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_dispatch.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_dispatch.rs @@ -18,15 +18,16 @@ use crate::bindings::_helpers::cache::native_store::current_code_identity; use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, keywords, }; +use crate::bindings::_helpers::compile_driver::stage_work::count_stage; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::native_host::user_code::UserCodeHost; use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::rules::catalogue::{ resolved_ignore_selectors, rule_catalogue, select_rules, }; +use crate::bindings::_helpers::rules::constants::RULES_MEMO_REUSES; use crate::bindings::_helpers::rules::constants::TYPE_PROOF_RULE_CODES; use crate::bindings::_helpers::rules::custom_evaluation::{ CustomRun, evaluate_custom_rules_cached, outcome, @@ -34,6 +35,9 @@ 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; use crate::bindings::_helpers::rules::rows::finalize_rule_findings_rows; +use crate::bindings::_helpers::rules::rows::{ + NativeRulesRequest, build_rules_request, evaluate_rules_request, +}; use crate::bindings::_helpers::rules::rule_plan::plan_rule_run; use crate::bindings::_helpers::rules::rule_run::finish_rule_run; use crate::bindings::_helpers::rules::selection::{rule_selected, selected_rule_codes}; @@ -309,7 +313,7 @@ fn evaluate_native<'py>( custom: CustomSource, ) -> PyResult> { let py: Python<'py> = run.project.py(); - let prepared: PyResult<(Bound<'py, PyAny>, Bound<'py, PyAny>)> = + let prepared: PyResult<(Bound<'py, PyAny>, Bound<'py, NativeRulesRequest>)> = native_request(run, custom_payloads); let custom: Option = match (custom, &prepared) { (CustomSource::Started(handle), Ok(_)) => handle, @@ -322,9 +326,8 @@ fn evaluate_native<'py>( (CustomSource::Deferred, Ok((payloads, _))) => start_custom_rules(run, payloads)?, (CustomSource::Deferred, Err(_)) => None, }; - let (_, request): (Bound<'py, PyAny>, Bound<'py, PyAny>) = prepared?; - let evaluated: PyResult> = native(py, NativeAttr::EvaluateRulesRequest) - .and_then(|evaluate| evaluate.call1((request,))); + let (_, request): (Bound<'py, PyAny>, Bound<'py, NativeRulesRequest>) = prepared?; + let evaluated: PyResult> = evaluate_rules_request(py, &request); cancel_on_interrupt(py, &evaluated, &run.cancel); let custom: PyResult> = match custom { Some(handle) => joined(py, handle, &run.cancel), @@ -348,6 +351,9 @@ fn evaluate_native<'py>( .into_any() }; let reused: bool = field(&native_result, "reused")?.is_truthy()?; + if reused { + count_stage(py, RULES_MEMO_REUSES, 1)?; + } let native_hits: i64 = field(&native_result, "cache_hits")?.extract()?; let native_misses: i64 = field(&native_result, "cache_misses")?.extract()?; let custom_hits: i64 = field(&custom, "cache_hits")?.extract()?; @@ -392,7 +398,7 @@ fn evaluate_native<'py>( fn native_request<'py>( run: &NativeRun<'py>, custom_payloads: Option>, -) -> PyResult<(Bound<'py, PyAny>, Bound<'py, PyAny>)> { +) -> PyResult<(Bound<'py, PyAny>, Bound<'py, NativeRulesRequest>)> { let py: Python<'py> = run.project.py(); let payloads: Bound<'py, PyAny> = match custom_payloads { Some(payloads) => payloads, @@ -408,25 +414,28 @@ fn native_request<'py>( for code in TYPE_PROOF_RULE_CODES { include_type_proof = include_type_proof || rule_selected(&run.config, code)?; } - let request: Bound<'py, PyAny> = call_with( - &native(py, NativeAttr::BuildRulesRequest)?, - &[ - ("project", run.project.clone()), - ("config", run.config.clone()), - ("project_dir", run.project_dir.clone()), - ("dialect", run.dialect.clone().into_bound_py_any(py)?), - ( - "initial_findings", - run.initial_findings - .clone() - .unwrap_or_else(|| PyTuple::empty(py).into_any()), - ), - ("custom_payloads", payloads.clone()), - ( - "include_type_proof", - include_type_proof.into_bound_py_any(py)?, - ), - ], + let request: Bound<'py, NativeRulesRequest> = Bound::new( + py, + build_rules_request(Some(&keywords( + py, + &[ + ("project", run.project.clone()), + ("config", run.config.clone()), + ("project_dir", run.project_dir.clone()), + ("dialect", run.dialect.clone().into_bound_py_any(py)?), + ( + "initial_findings", + run.initial_findings + .clone() + .unwrap_or_else(|| PyTuple::empty(py).into_any()), + ), + ("custom_payloads", payloads.clone()), + ( + "include_type_proof", + include_type_proof.into_bound_py_any(py)?, + ), + ], + )?))?, )?; Ok((payloads, request)) } @@ -539,10 +548,6 @@ fn cancel_on_interrupt(py: Python<'_>, result: &PyResult>, canc } } -fn native<'py>(py: Python<'py>, name: NativeAttr) -> PyResult> { - registry::native(py, name) -} - pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { module.add_function(wrap_pyfunction!(evaluate_project_rules, module)?)?; module.add_function(wrap_pyfunction!(evaluate_native_rules, module)?)?; 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 909afc238f..db0f001347 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_reports.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_reports.rs @@ -317,7 +317,7 @@ pub(crate) fn serialize_scope_report( /// `scope_metadata_projection` of one `ScopeIndex`, as the dictionary Python builds. #[pyfunction] #[pyo3(name = "scope_metadata_projection", signature = (*, index))] -fn metadata_projection(py: Python<'_>, index: &Bound<'_, PyAny>) -> PyResult { +pub(crate) fn metadata_projection(py: Python<'_>, index: &Bound<'_, PyAny>) -> PyResult { let index: ProjectionIndex = projection_index(read_index(index)?); let projection: JsonValue = scope_metadata_projection(&index); json_to_python(py, projection, false) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/completion.rs b/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/completion.rs index 15d805883f..5408cdde70 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/completion.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/completion.rs @@ -11,6 +11,7 @@ use sqlbuild_analysis::semantic_checks::models::{ use crate::bindings::_helpers::analysis::model_reads::{inferred_names, lineage_inputs}; use crate::bindings::_helpers::boundary::panics::compiler_error; use crate::bindings::_helpers::compiled_project::compiled_model_view::LineageInput; +use crate::bindings::_helpers::compiled_project::records::with_binding_diagnostics; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::semantic_checks::failures::semantic_error; use crate::bindings::_helpers::semantic_checks::metadata::{ShapeRows, shape_rows}; @@ -81,12 +82,9 @@ pub(crate) fn completed_diagnostics<'py>( .into_iter() .map(|position| rebuilt[position].clone()) .collect(); - updated.push(classes.call( - &classes.with_binding_diagnostics, - &[ - ("model", model), - ("diagnostics", &PyTuple::new(py, selected)?.into_any()), - ], + updated.push(with_binding_diagnostics( + model, + &PyTuple::new(py, selected)?.into_any(), )?); } PyTuple::new(py, updated)?.into_any() diff --git a/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/constants.rs new file mode 100644 index 0000000000..a134a86130 --- /dev/null +++ b/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/constants.rs @@ -0,0 +1,4 @@ +//! Stage-work names the semantic completion stage counts. + +/// Single-model schema revalidations run after type recovery. +pub(crate) const SCHEMA_REVALIDATIONS: &str = "schema_revalidations"; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/mod.rs b/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/mod.rs index 5075c7d3f0..692d959381 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/mod.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/mod.rs @@ -1,4 +1,5 @@ pub(crate) mod completion; +pub(crate) mod constants; pub(crate) mod failures; pub(crate) mod help_catalogue; pub(crate) mod inputs; 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 ad2b999942..88ef5a4e07 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,11 +1,12 @@ //! The Python models, enums and compile helpers the semantic completion stage reads and builds. +use crate::bindings::_helpers::compiled_project::records::project_with; use crate::bindings::_helpers::native_host::adapter::AdapterHost; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::records::construct::{enum_member, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::fields::replaced; +use crate::bindings::_helpers::records::ids::RecordId; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyString}; @@ -13,7 +14,6 @@ const COMPILE_TYPES_MODULE: &str = "sqlbuild.compiler.compile.types"; const UNNEEDED_SQL_ANALYSIS_OPT_OUT_CODE: &str = "P009"; const SQL_ANALYSIS_CONFIG_KEY: &str = "sql_analysis"; -const DATACLASSES_MODULE: &str = "dataclasses"; /// Imported once per stage run. pub(crate) struct StageClasses<'py> { @@ -27,11 +27,6 @@ pub(crate) struct StageClasses<'py> { pub(crate) source_resource: Bound<'py, PyAny>, pub(crate) sql_test_resource: Bound<'py, PyAny>, pub(crate) opt_out_code: Bound<'py, PyAny>, - pub(crate) replace: Bound<'py, PyAny>, - pub(crate) project_with: Bound<'py, PyAny>, - pub(crate) with_type_recovery: Bound<'py, PyAny>, - pub(crate) with_binding_diagnostics: Bound<'py, PyAny>, - pub(crate) schema_validations: Bound<'py, PyAny>, pub(crate) normalize_type: Bound<'py, PyAny>, pub(crate) sql_analysis_key: Bound<'py, PyAny>, pub(crate) udf_kind: Bound<'py, PyAny>, @@ -55,11 +50,6 @@ impl<'py> StageClasses<'py> { source_resource: native_field(&resource, "SOURCE")?, sql_test_resource: native_field(&resource, "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: 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: native_field(&kinds, "UDF")?, @@ -84,17 +74,17 @@ impl<'py> StageClasses<'py> { callable.call((), Some(&kwargs)) } - /// `dataclasses.replace(value, **pairs)`. + /// `value` with `pairs` replaced. pub(crate) fn replaced( &self, value: &Bound<'py, PyAny>, pairs: &[(&str, &Bound<'py, PyAny>)], ) -> PyResult> { - let kwargs: Bound<'py, PyDict> = PyDict::new(self.py()); - for (name, value) in pairs { - kwargs.set_item(*name, *value)?; - } - self.replace.call((value,), Some(&kwargs)) + let owned: Vec<(&str, Bound<'py, PyAny>)> = pairs + .iter() + .map(|(name, changed)| (*name, (*changed).clone())) + .collect(); + replaced(value, &owned) } /// `project_with(project=project, **pairs)`. @@ -103,8 +93,10 @@ impl<'py> StageClasses<'py> { project: &Bound<'py, PyAny>, pairs: &[(&str, &Bound<'py, PyAny>)], ) -> PyResult> { - let mut all: Vec<(&str, &Bound<'py, PyAny>)> = vec![("project", project)]; - all.extend_from_slice(pairs); - self.call(&self.project_with, &all) + let kwargs: Bound<'py, PyDict> = PyDict::new(self.py()); + for (name, value) in pairs { + kwargs.set_item(*name, *value)?; + } + project_with(project, Some(kwargs)) } } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/type_recovery.rs b/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/type_recovery.rs index ca4dff59d9..2e99060e06 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/type_recovery.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/type_recovery.rs @@ -12,11 +12,15 @@ use sqlbuild_analysis::semantic_checks::models::{ use sqlbuild_analysis::semantic_checks::types::RevisedBinding; use sqlbuild_analysis::semantic_validation::models as validation; +use crate::bindings::_helpers::analysis::binding_catalog::schema_validations; use crate::bindings::_helpers::analysis::model_reads::{inferred_names, lineage_inputs}; use crate::bindings::_helpers::analysis_session::session::NativeModelAnalysisSession; use crate::bindings::_helpers::boundary::panics::compiler_error; +use crate::bindings::_helpers::compile_driver::stage_work::count_stage; use crate::bindings::_helpers::compiled_project::compiled_model_view::LineageInput; +use crate::bindings::_helpers::compiled_project::records::with_type_recovery; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::semantic_checks::constants::SCHEMA_REVALIDATIONS; use crate::bindings::_helpers::semantic_checks::failures::{internal_error, semantic_error}; use crate::bindings::_helpers::semantic_checks::inputs::revalidation_request; use crate::bindings::_helpers::semantic_checks::session_facts::{ @@ -107,20 +111,17 @@ pub(crate) fn recovered_types<'py>( } kept.push(classes.replaced(original, &[("notes", &PyTuple::new(py, notes)?.into_any())])?); } - let poisoned_outputs: Bound<'py, PyAny> = PyFrozenSet::new(py, &poisoned)?.into_any(); + let poisoned_outputs: Bound<'py, PyFrozenSet> = PyFrozenSet::new(py, &poisoned)?; let mut recovered: Vec> = Vec::with_capacity(models.len()); for (model, positions) in models.iter().zip(outcome.model_bindings) { let selected: Vec> = positions .into_iter() .map(|position| kept[position].clone()) .collect(); - recovered.push(classes.call( - &classes.with_type_recovery, - &[ - ("model", model), - ("diagnostics", &PyTuple::new(py, selected)?.into_any()), - ("poisoned", &poisoned_outputs), - ], + recovered.push(with_type_recovery( + model, + &PyTuple::new(py, selected)?.into_any(), + &poisoned_outputs, )?); } classes.project_with( @@ -270,12 +271,9 @@ fn revalidated<'py>( for index in models { let request: Bound<'py, PyAny> = revalidation_request(project, &project_models.get_item(*index)?, &unknown)?; - let result: Bound<'py, PyAny> = classes - .call( - &classes.schema_validations, - &[("requests", &PyTuple::new(py, [request])?.into_any())], - )? - .get_item(0)?; + count_stage(py, SCHEMA_REVALIDATIONS, 1)?; + let result: Bound<'py, PyAny> = + schema_validations(py, &PyTuple::new(py, [request])?.into_any())?.get_item(0)?; let mut rows: Vec = Vec::new(); for item in native_field(&result, "diagnostics")?.try_iter()? { let item: Bound<'py, PyAny> = item?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/semantic_shapes/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/semantic_shapes/constants.rs new file mode 100644 index 0000000000..d1ffa807bf --- /dev/null +++ b/crates/sqlbuild-python/src/bindings/_helpers/semantic_shapes/constants.rs @@ -0,0 +1,4 @@ +//! Stage-work names the semantic shape stage counts. + +/// Model queries analysed again against inspected source columns. +pub(crate) const SOURCE_REBINDING_ANALYSES: &str = "source_rebinding_analyses"; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/semantic_shapes/mod.rs b/crates/sqlbuild-python/src/bindings/_helpers/semantic_shapes/mod.rs index 9afb6d4c00..7f20b6c6ef 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/semantic_shapes/mod.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/semantic_shapes/mod.rs @@ -1,3 +1,4 @@ +pub(crate) mod constants; pub(crate) mod coverage; pub(crate) mod declarations; pub(crate) mod registration; 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 385c35252b..02de9f6925 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 @@ -12,12 +12,16 @@ use crate::bindings::_helpers::analysis::binding_catalog::{ use crate::bindings::_helpers::analysis::schema_request::{ SchemaRequestParts, schema_validation_request, }; +use crate::bindings::_helpers::analysis_session::stage_functions::infer_native_query_columns; use crate::bindings::_helpers::attachments::cursor_intrinsics::analysis_cursor_sql; use crate::bindings::_helpers::boundary::keyword_request::Request; +use crate::bindings::_helpers::compile_driver::python_calls::keywords; +use crate::bindings::_helpers::compile_driver::stage_work::count_stage; use crate::bindings::_helpers::records::construct::{enum_from_value, enum_member, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::semantic_shapes::constants::SOURCE_REBINDING_ANALYSES; use crate::bindings::_helpers::semantic_shapes::declarations::{declared_types, function_names}; use crate::bindings::_helpers::semantic_shapes::shapes::{ model_inputs, project_shapes, published_shape, @@ -156,7 +160,6 @@ fn rebound_models<'py>( .call_method0("static_order")?; let query_class: Object<'py> = record_class(py, RecordId::NativeColumnQuery)?; let unknown: Object<'py> = enum_member(py, RecordId::InferredNullability, "UNKNOWN")?; - 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()? { @@ -206,15 +209,16 @@ fn rebound_models<'py>( ("references", native_field(&model, "references")?), ], )?; - let analysis: Object<'py> = call( - &infer, + count_stage(py, SOURCE_REBINDING_ANALYSES, 1)?; + let analysis: Object<'py> = infer_native_query_columns(Some(&keywords( + py, &[ ("queries", PyTuple::new(py, [query])?.into_any()), ("profile", profile.clone()), ("column_types_by_table", inputs.clone().into_any()), ("column_nullability_by_table", nullability.into_any()), ], - )? + )?))? .get_item(0)?; let inferred: Object<'py> = native_field(&analysis, "columns")?; let mut all_inputs_closed: bool = true; diff --git a/tests/integration/src/sqlbuild/cli/commands/main/helpers.py b/tests/integration/src/sqlbuild/cli/commands/main/helpers.py index 09311ab352..d223da16ea 100644 --- a/tests/integration/src/sqlbuild/cli/commands/main/helpers.py +++ b/tests/integration/src/sqlbuild/cli/commands/main/helpers.py @@ -19,7 +19,6 @@ from sqlbuild._native import ( CompilerStage, CustomHostObserver, - NativeQueryColumns, NativeSqlTestArtifact, NativeSqlTestPlanningError, PlannedStaticSqlTests, @@ -671,24 +670,10 @@ def write_expression_source_project( (project_dir / "models" / "orders.sql").write_text(model_sql, encoding="utf-8") -def record_source_rebinding_analyses(monkeypatch: pytest.MonkeyPatch) -> list[str]: - """Record every model query analysed again against inspected source columns.""" +def source_rebinding_analyses() -> int: + """Model queries analysed again against inspected source columns since the last reset.""" - analysed: list[str] = [] - original: Callable[..., tuple[NativeQueryColumns, ...]] = ( - native_module.infer_native_query_columns - ) - - def recording(**kwargs: Any) -> tuple[NativeQueryColumns, ...]: - analysed.extend(query.sql for query in filter(_reanalysis, kwargs["queries"])) - return original(**kwargs) - - monkeypatch.setattr(native_module, "infer_native_query_columns", recording) - return analysed - - -def _reanalysis(query: Any) -> bool: - return query.mode == "reanalysis" + return native_module.compile_stage_work_info().get("source_rebinding_analyses", 0) def eager_output_column_scans(*, capture_dir: Path) -> tuple[int, int]: diff --git a/tests/integration/src/sqlbuild/cli/commands/main/test_source_rebinding.py b/tests/integration/src/sqlbuild/cli/commands/main/test_source_rebinding.py index 9236a8a81a..3be6b65600 100644 --- a/tests/integration/src/sqlbuild/cli/commands/main/test_source_rebinding.py +++ b/tests/integration/src/sqlbuild/cli/commands/main/test_source_rebinding.py @@ -6,11 +6,12 @@ import pytest +import sqlbuild._native as native from sqlbuild._native import run_cli as main from tests.integration.src.sqlbuild.cli.commands.main._test_types import SourceRebindingTestCase from tests.integration.src.sqlbuild.cli.commands.main.helpers import ( execute_duckdb_sql, - record_source_rebinding_analyses, + source_rebinding_analyses, write_expression_source_project, ) @@ -45,7 +46,6 @@ def test_given_expression_source_when_planning_then_rebinds_only_on_new_column_e test_case: SourceRebindingTestCase, tmp_path: Path, capsys: pytest.CaptureFixture[str], - monkeypatch: pytest.MonkeyPatch, ) -> None: execute_duckdb_sql( db_path=tmp_path / "orders.duckdb", @@ -56,7 +56,7 @@ def test_given_expression_source_when_planning_then_rebinds_only_on_new_column_e source_expression=test_case.source_expression, model_sql=test_case.model_sql, ) - analysed: list[str] = record_source_rebinding_analyses(monkeypatch) + _ = native.compile_stage_work_info(reset=True) _ = capsys.readouterr() exit_code: int = main(["--project-dir", str(tmp_path), "--no-color", "plan"]) @@ -64,7 +64,7 @@ def test_given_expression_source_when_planning_then_rebinds_only_on_new_column_e output: str = "".join(capsys.readouterr()) assert exit_code == test_case.expected_exit_code, output assert test_case.expected_fragment in output - assert bool(analysed) is test_case.expected_rebinding + assert (source_rebinding_analyses() > 0) is test_case.expected_rebinding if __name__ == "__main__": diff --git a/tests/integration/src/sqlbuild/compiler/pipeline/test_semantic_triage.py b/tests/integration/src/sqlbuild/compiler/pipeline/test_semantic_triage.py index 298e7d7a45..25e8701f84 100644 --- a/tests/integration/src/sqlbuild/compiler/pipeline/test_semantic_triage.py +++ b/tests/integration/src/sqlbuild/compiler/pipeline/test_semantic_triage.py @@ -3,7 +3,6 @@ import json from pathlib import Path from typing import Any -from unittest.mock import Mock import pytest @@ -211,7 +210,6 @@ def test_given_cached_validation_when_warm_or_schema_changed_then_reuses_only_ma test_case: SemanticTriageCase, tmp_path: Path, capsys: pytest.CaptureFixture[str], - monkeypatch: pytest.MonkeyPatch, ) -> None: (tmp_path / "sqlbuild_project.toml").write_text( 'name = "orders"\nadapter = "duckdb"\n[rules]\nselect = []\n' @@ -225,22 +223,15 @@ def test_given_cached_validation_when_warm_or_schema_changed_then_reuses_only_ma 'MODEL (description "Test model totals.", materialized view);\nSELECT quantity + 1 AS total FROM __source("orders")' ) args: list[str] = ["--project-dir", str(tmp_path), "compile", "--json"] - with monkeypatch.context() as patch: - repeated_validation: Mock = Mock( - side_effect=AssertionError("cold compile repeated fused validation") - ) - patch.setattr(native, "schema_validations", repeated_validation) - assert main(args) == 0 - repeated_validation.assert_not_called() + _ = native.compile_stage_work_info(reset=True) + assert main(args) == 0 + cold_work: dict[str, int] = native.compile_stage_work_info(reset=True) + assert cold_work.get("schema_revalidations", 0) == 0 + assert cold_work["complete_semantic_project"] == 1 capsys.readouterr() - with monkeypatch.context() as patch: - forbidden: Mock = Mock( - side_effect=AssertionError("warm compile repeated native semantic work") - ) - patch.setattr(native, "schema_validations", forbidden) - patch.setattr(native, "analyze_project_queries_compact_json", forbidden) - assert main(args) == 0 - forbidden.assert_not_called() + assert main(args) == 0 + warm_work: dict[str, int] = native.compile_stage_work_info(reset=True) + assert warm_work.get("schema_revalidations", 0) == 0 capsys.readouterr() source.write_text(declaration.replace("INTEGER", "TIMESTAMP")) assert main(args) == 1 diff --git a/tests/unit/src/sqlbuild/rule_engine/_helpers/engine/helpers.py b/tests/unit/src/sqlbuild/rule_engine/_helpers/engine/helpers.py index 802272118d..7e27738425 100644 --- a/tests/unit/src/sqlbuild/rule_engine/_helpers/engine/helpers.py +++ b/tests/unit/src/sqlbuild/rule_engine/_helpers/engine/helpers.py @@ -1,14 +1,11 @@ """Test helpers for native rules engine boundaries.""" import json -from collections.abc import Callable from dataclasses import dataclass, field from operator import attrgetter from pathlib import Path from typing import Any, cast -import pytest - import sqlbuild._native as native from sqlbuild._native import ( MIN_CUSTOM_RULE_TEST_CASES, @@ -23,7 +20,6 @@ DiscoveredSqlTestBlock, DiscoveredSqlTestFile, Finding, - NativeRulesEvaluation, NativeRulesRequest, Rule, RuleExemption, @@ -51,10 +47,6 @@ _MODEL_SQL: str = "WITH final AS (SELECT 1 AS id) SELECT id FROM final" -_BUILD_RULES_REQUEST: Callable[..., NativeRulesRequest] = native.build_rules_request -_SCOPE_PROJECTION: Callable[..., dict[str, Any]] = native.scope_metadata_projection - - def project_with_scope(*, index: ScopeIndex) -> CompiledProjectView: """Return a minimal compiled project carrying canonical scope facts.""" @@ -67,44 +59,33 @@ def project_with_scope(*, index: ScopeIndex) -> CompiledProjectView: return project_with(project=project, scope_index=index) -def captured_native_request( - *, monkeypatch: pytest.MonkeyPatch, project: CompiledProjectView, project_dir: Path -) -> dict[str, Any]: - """Evaluate through the Python boundary and capture the header and rows handed to Rust.""" - - captured: dict[str, Any] = {} - - def scope_metadata_projection(**kwargs: Any) -> dict[str, Any]: - projection: dict[str, Any] = _SCOPE_PROJECTION(**kwargs) - captured.update(scope_index=projection) - return projection - - def build_rules_request(**kwargs: Any) -> object: - request: NativeRulesRequest = _BUILD_RULES_REQUEST(**kwargs) - captured.update(cast(dict[str, Any], json.loads(request.sql_facts_json()))) - return request - - def evaluate_rules_request(request: object) -> NativeRulesEvaluation: - return NativeRulesEvaluation( - findings=(), - selected_codes=(), - evaluated_models=0, - cache_hits=0, - cache_misses=0, - built_in_ms=0, - reused=False, - ) +def present_metadata(scope_index: dict[str, Any]) -> dict[str, Any]: + """`scope_index` with each declaration's absent metadata kinds dropped, as projected.""" + + declarations: list[dict[str, Any]] = [ + {**declaration, "metadata": _present(declaration["metadata"])} + for declaration in scope_index["declarations"] + ] + return {**scope_index, "declarations": declarations} + + +def _present(metadata: dict[str, Any]) -> dict[str, Any]: + return dict(filter(lambda item: item[1] is not None, metadata.items())) + + +def captured_native_request(*, project: CompiledProjectView, project_dir: Path) -> dict[str, Any]: + """Build the native rules request for `project` and return the facts it carries.""" - monkeypatch.setattr(native, "scope_metadata_projection", scope_metadata_projection) - monkeypatch.setattr(native, "build_rules_request", build_rules_request) - monkeypatch.setattr(native, "evaluate_rules_request", evaluate_rules_request) - native.evaluate_native_rules( + request: NativeRulesRequest = native.build_rules_request( project=project, config=RulesConfig(cache=RulesCacheConfig(enabled=False)), project_dir=project_dir, - catalogue=(), + dialect="generic", + initial_findings=(), + custom_payloads=[], + include_type_proof=False, ) - return captured + return cast(dict[str, Any], json.loads(request.sql_facts_json())) def direct_sql_test(*, mode: SqlTestMode, name: str, block_index: int) -> CompiledSqlTest: diff --git a/tests/unit/src/sqlbuild/rule_engine/_helpers/engine/test_native_facts.py b/tests/unit/src/sqlbuild/rule_engine/_helpers/engine/test_native_facts.py index e6852906cd..87df1531a2 100644 --- a/tests/unit/src/sqlbuild/rule_engine/_helpers/engine/test_native_facts.py +++ b/tests/unit/src/sqlbuild/rule_engine/_helpers/engine/test_native_facts.py @@ -79,7 +79,6 @@ def test_given_compiled_sql_facts_when_evaluating_native_then_exact_safe_rows_ar ) request: dict[str, Any] = captured_native_request( - monkeypatch=monkeypatch, project=project_with(project=project, sql_tests=sql_tests, sql_scenarios=(scenario,)), project_dir=tmp_path, ) diff --git a/tests/unit/src/sqlbuild/rule_engine/_helpers/engine/test_scope_facts.py b/tests/unit/src/sqlbuild/rule_engine/_helpers/engine/test_scope_facts.py index 50a7f3336b..b16ac8ec25 100644 --- a/tests/unit/src/sqlbuild/rule_engine/_helpers/engine/test_scope_facts.py +++ b/tests/unit/src/sqlbuild/rule_engine/_helpers/engine/test_scope_facts.py @@ -15,6 +15,7 @@ from tests.unit.src.sqlbuild.rule_engine._helpers.engine._test_types import ScopePayloadTestCase from tests.unit.src.sqlbuild.rule_engine._helpers.engine.helpers import ( captured_native_request, + present_metadata, project_with_scope, ) @@ -30,12 +31,11 @@ def test_given_compiler_scope_index_when_evaluating_native_then_payload_equals_s index: ScopeIndex = scope_index() request: dict[str, Any] = captured_native_request( - monkeypatch=monkeypatch, project=project_with_scope(index=index), project_dir=tmp_path, ) - assert (request["scope_index"] == scope_metadata_projection(index=index)) is ( + assert (present_metadata(request["scope_index"]) == scope_metadata_projection(index=index)) is ( test_case.expected_result ) assert request["scope_index"]["declarations"][0]["metadata"]["constant"] == { @@ -68,12 +68,10 @@ def test_given_complete_and_partial_scope_indexes_when_evaluating_then_completen ) complete_request: dict[str, Any] = captured_native_request( - monkeypatch=monkeypatch, project=project_with_scope(index=complete), project_dir=tmp_path, ) partial_request: dict[str, Any] = captured_native_request( - monkeypatch=monkeypatch, project=project_with_scope(index=partial), project_dir=tmp_path, ) @@ -104,12 +102,10 @@ def test_given_unsorted_and_absolute_scope_facts_when_projecting_then_order_is_s second: ScopeIndex = replace(first, declarations=tuple(reversed(first.declarations))) first_request: dict[str, Any] = captured_native_request( - monkeypatch=monkeypatch, project=project_with_scope(index=first), project_dir=tmp_path, ) second_request: dict[str, Any] = captured_native_request( - monkeypatch=monkeypatch, project=project_with_scope(index=second), project_dir=tmp_path, ) diff --git a/tests/unit/src/sqlbuild/rule_engine/_helpers/run/helpers.py b/tests/unit/src/sqlbuild/rule_engine/_helpers/run/helpers.py index 6a7b516f4f..4051569c8f 100644 --- a/tests/unit/src/sqlbuild/rule_engine/_helpers/run/helpers.py +++ b/tests/unit/src/sqlbuild/rule_engine/_helpers/run/helpers.py @@ -1,25 +1,12 @@ -from collections.abc import Callable - import pytest -import sqlbuild._native as native from sqlbuild import _native -from sqlbuild._native import NativeRulesEvaluation, NativeRulesRequest - - -def record_reuse(*, monkeypatch: pytest.MonkeyPatch) -> list[bool]: - """Record whether each natively evaluated request was answered from the response memo.""" - reused: list[bool] = [] - evaluate: Callable[[NativeRulesRequest], NativeRulesEvaluation] = _native.evaluate_rules_request - def recording(request: NativeRulesRequest) -> NativeRulesEvaluation: - evaluation: NativeRulesEvaluation = evaluate(request) - reused.append(evaluation.reused) - return evaluation +def memo_reuses() -> int: + """Native rules evaluations answered from the response memo since the last reset.""" - monkeypatch.setattr(native, "evaluate_rules_request", recording) - return reused + return _native.compile_stage_work_info().get("rules_memo_reuses", 0) def rebuild_identity(*, monkeypatch: pytest.MonkeyPatch) -> None: diff --git a/tests/unit/src/sqlbuild/rule_engine/_helpers/run/test_native_rows.py b/tests/unit/src/sqlbuild/rule_engine/_helpers/run/test_native_rows.py index 3e1d2306a4..fb97c862b8 100644 --- a/tests/unit/src/sqlbuild/rule_engine/_helpers/run/test_native_rows.py +++ b/tests/unit/src/sqlbuild/rule_engine/_helpers/run/test_native_rows.py @@ -13,8 +13,8 @@ NativeRowsMemoTestCase, ) from tests.unit.src.sqlbuild.rule_engine._helpers.run.helpers import ( + memo_reuses, rebuild_identity, - record_reuse, ) from tests.unit.src.sqlbuild.rule_engine.main.evaluate.helpers import build_project @@ -59,19 +59,23 @@ def test_given_memoized_response_from_another_build_when_evaluating_natively_the tmp_path: Path, test_case: NativeRowsMemoTestCase, ) -> None: - reused: list[bool] = record_reuse(monkeypatch=monkeypatch) + _ = native.compile_stage_work_info(reset=True) + reused: list[bool] = [] config_values: dict[str, object] = {"materialized": "table"} cold: RulesResult = evaluate_contract_rule( config_values=config_values, project_dir=tmp_path, cache_enabled=True ) + reused.append(memo_reuses() == 1) rebuild_identity(monkeypatch=monkeypatch) rebuilt: RulesResult = evaluate_contract_rule( config_values=config_values, project_dir=tmp_path, cache_enabled=True ) + reused.append(memo_reuses() == 1) warm: RulesResult = evaluate_contract_rule( config_values=config_values, project_dir=tmp_path, cache_enabled=True ) + reused.append(memo_reuses() == 1) assert tuple(reused) == test_case.expected_reused assert rebuilt.findings == warm.findings == cold.findings From a9629bb4f63e4f7cc74d2e7db7feb8dd1f5cd1fe Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Sat, 17 Oct 2026 03:20:20 +0100 Subject: [PATCH 08/28] refactor: report compile diagnostics and macro store tokens through direct calls Scope, SQL reference and macro expansion bindings call report_compile_diagnostic, macro_store_token and context_store_token directly instead of carrying the sqlbuild._native callables in their class tables. --- .../_helpers/macro_bridge/store_keys.rs | 4 ++-- .../_helpers/render/macro_call_classes.rs | 23 +++---------------- .../_helpers/render/macro_expansion.rs | 7 +----- .../_helpers/render/macro_expansion_result.rs | 20 +--------------- .../render/macro_generated_references.rs | 14 ++++------- .../_helpers/scopes/compile_usages.rs | 13 ++++------- .../bindings/_helpers/scopes/scope_errors.rs | 10 +++----- .../_helpers/sqltext/sql_references.rs | 17 +++++++------- 8 files changed, 28 insertions(+), 80 deletions(-) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/macro_bridge/store_keys.rs b/crates/sqlbuild-python/src/bindings/_helpers/macro_bridge/store_keys.rs index 3431f09483..3fbbcce94a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/macro_bridge/store_keys.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/macro_bridge/store_keys.rs @@ -218,7 +218,7 @@ impl<'py> CanonicalTypes<'py> { /// Name a macro by its name, file, identity and exact source, or None when it has no key. #[pyfunction] #[pyo3(signature = (**options))] -fn macro_store_token<'py>( +pub(crate) fn macro_store_token<'py>( py: Python<'py>, options: Option>, ) -> PyResult> { @@ -251,7 +251,7 @@ fn macro_store_token<'py>( /// Digest the whole context a macro may read, or None when a value has no stable text. #[pyfunction] #[pyo3(signature = (**options))] -fn context_store_token<'py>( +pub(crate) fn context_store_token<'py>( py: Python<'py>, options: Option>, ) -> PyResult> { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/render/macro_call_classes.rs b/crates/sqlbuild-python/src/bindings/_helpers/render/macro_call_classes.rs index 32828a6b24..efd7dddff9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/render/macro_call_classes.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/render/macro_call_classes.rs @@ -2,12 +2,12 @@ use std::collections::HashMap; -use pyo3::FromPyObject; use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods}; use sqlbuild_render::macro_calls::main::call_class_store_text::call_class_store_text; +use crate::bindings::_helpers::macro_bridge::store_keys::{context_store_token, macro_store_token}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::render::macro_expansion_facts::ResourceRef; @@ -32,14 +32,6 @@ pub(crate) struct CallClass { persistent: bool, } -/// `macro_store_token` and `context_store_token`, the value text of a stored call's inputs. -#[derive(FromPyObject)] -#[pyo3(from_item_all)] -pub(crate) struct StoreTokenClasses<'py> { - macro_token: Bound<'py, PyAny>, - context_token: Bound<'py, PyAny>, -} - /// The call classes, class ids and tokens one compile's memo has seen. #[derive(Debug, Default)] pub(crate) struct CallClassState { @@ -67,7 +59,6 @@ pub(crate) struct ClassInputs<'a, 'py> { pub(crate) declarations: &'a Bound<'py, PyAny>, pub(crate) loaded_macros: &'a Bound<'py, PyDict>, pub(crate) macro_context: &'a Bound<'py, PyAny>, - pub(crate) store: &'a StoreTokenClasses<'py>, } impl CallClassState { @@ -191,11 +182,7 @@ impl CallClassState { let arguments: Bound<'_, PyDict> = PyDict::new(py); arguments.set_item("loaded", &loaded)?; arguments.set_item("identity", &identity)?; - inputs - .store - .macro_token - .call((), Some(&arguments))? - .extract()? + macro_store_token(py, Some(arguments))? } else { None }; @@ -239,11 +226,7 @@ impl CallClassState { let arguments: Bound<'_, PyDict> = PyDict::new(owner.py()); arguments.set_item("macro_context", inputs.macro_context)?; arguments.set_item("declarations", declarations)?; - inputs - .store - .context_token - .call((), Some(&arguments))? - .extract()? + context_store_token(owner.py(), Some(arguments))? } else { None }; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/render/macro_expansion.rs b/crates/sqlbuild-python/src/bindings/_helpers/render/macro_expansion.rs index 1545aedf17..4c6dae0d0d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/render/macro_expansion.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/render/macro_expansion.rs @@ -17,9 +17,7 @@ use sqlbuild_render::macro_calls::models::{ use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::render::declaration_expansion::usage_visibility; -use crate::bindings::_helpers::render::macro_call_classes::{ - CallClass, ClassInputs, StoreTokenClasses, -}; +use crate::bindings::_helpers::render::macro_call_classes::{CallClass, ClassInputs}; use crate::bindings::_helpers::render::macro_call_memo::{MacroCallMemo, poisoned}; use crate::bindings::_helpers::render::macro_call_rows::{SpanRow, span_row}; use crate::bindings::_helpers::render::macro_evaluation::{CallText, Evaluation, evaluate_call}; @@ -63,8 +61,6 @@ pub(crate) struct ExpansionClasses<'py> { pub(crate) ref_kinds: Bound<'py, PyDict>, /// Typed reference function by `SqlResourceRefKind` value. ref_functions: Bound<'py, PyDict>, - /// `macro_store_token` and `context_store_token`. - store_tokens: StoreTokenClasses<'py>, pub(crate) relation_renderer: Bound<'py, PyAny>, /// Builds the `MacroContext` a macro is called with from the visible declarations. pub(crate) invocation_context: Bound<'py, PyAny>, @@ -180,7 +176,6 @@ impl NativeMacroExpansion { declarations: self.declarations.bind(py), loaded_macros: self.loaded_macros.bind(py), macro_context: self.macro_context.bind(py), - store: &classes.store_tokens, }; let mut state = memo.classes.lock().map_err(poisoned)?; let mut found: Vec = Vec::with_capacity(scan.sites.len()); 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 ba98c8c033..72d511ab8b 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 @@ -16,7 +16,7 @@ use crate::bindings::_helpers::records::construct::{ enum_member, new_record, new_record_with, record_class, }; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::ids::{ErrorId, 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; @@ -68,10 +68,6 @@ fn generated_classes(py: Python<'_>) -> PyResult> { "compiler_diagnostic", record_class(py, RecordId::CompilerDiagnostic)?, ), - ( - "report_diagnostic", - registry::native(py, NativeAttr::ReportCompileDiagnostic)?, - ), ( "source_location", record_class(py, RecordId::SourceLocation)?, @@ -99,19 +95,6 @@ fn built_classes(py: Python<'_>) -> PyResult> { ref_kinds.set_item(function, ref_kind.call1((kind,))?)?; ref_functions.set_item(kind, function)?; } - let store_tokens: Bound<'_, PyDict> = keywords( - py, - &[ - ( - "macro_token", - registry::native(py, NativeAttr::MacroStoreToken)?, - ), - ( - "context_token", - registry::native(py, NativeAttr::ContextStoreToken)?, - ), - ], - )?; keywords( py, &[ @@ -149,7 +132,6 @@ fn built_classes(py: Python<'_>) -> PyResult> { ), ("ref_kinds", ref_kinds.into_any()), ("ref_functions", ref_functions.into_any()), - ("store_tokens", store_tokens.into_any()), ( "relation_renderer", host::entry(py, UserCodeHost::RelationRenderer)?, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/render/macro_generated_references.rs b/crates/sqlbuild-python/src/bindings/_helpers/render/macro_generated_references.rs index 8cb827b0b4..47824e589f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/render/macro_generated_references.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/render/macro_generated_references.rs @@ -11,6 +11,7 @@ use sqlbuild_sqltext::sql_references::models::{ }; use sqlbuild_sqltext::sql_scan::models::LexicalSyntax; +use crate::bindings::_helpers::compile_driver::diagnostic_collector::report_compile_diagnostic; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::sqltext::lexical_syntax::LexicalSyntaxInput; @@ -31,7 +32,6 @@ const MODEL_KIND: &str = "model"; #[pyo3(from_item_all)] pub(crate) struct GeneratedReferenceClasses<'py> { compiler_diagnostic: Bound<'py, PyAny>, - report_diagnostic: Bound<'py, PyAny>, source_location: Bound<'py, PyAny>, resource_identity: Bound<'py, PyAny>, generic_syntax: LexicalSyntaxInput, @@ -247,13 +247,9 @@ impl<'py> Reporter<'_, 'py> { } fn report(&self, key: Bound<'py, PyTuple>, fields: &Bound<'py, PyDict>) -> PyResult<()> { - let arguments: Bound<'py, PyDict> = PyDict::new(self.py()); - arguments.set_item("key", key)?; - arguments.set_item( - "diagnostic", - self.classes.compiler_diagnostic.call((), Some(fields))?, - )?; - let _ = self.classes.report_diagnostic.call((), Some(&arguments))?; - Ok(()) + report_compile_diagnostic( + key.as_any(), + &self.classes.compiler_diagnostic.call((), Some(fields))?, + ) } } 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 6c423b42d9..84afd1aa60 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/compile_usages.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/compile_usages.rs @@ -18,10 +18,11 @@ use sqlbuild_scopes::scope_index::models::{ DeclarationKind, DeclaredScope, ResourceIdentity, ResourceKind, ScopeKind, }; +use crate::bindings::_helpers::compile_driver::diagnostic_collector::report_compile_diagnostic; use crate::bindings::_helpers::compiled_project::model_loop_named::scope_path_message; use crate::bindings::_helpers::records::construct::new_record; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; const INACCESSIBLE_DECLARATION_CODE: &str = "S006"; @@ -189,7 +190,6 @@ fn report_inaccessible(py: Python<'_>, diagnostics: Vec) -> PyRes if diagnostics.is_empty() { return Ok(()); } - let report = registry::native(py, NativeAttr::ReportCompileDiagnostic)?; let path_class = py.import("pathlib")?.getattr("Path")?; for (key, message, path, help) in diagnostics { let fields: Bound<'_, PyDict> = PyDict::new(py); @@ -199,13 +199,10 @@ fn report_inaccessible(py: Python<'_>, diagnostics: Vec) -> PyRes fields.set_item("message", message)?; fields.set_item("path", path_class.call1((path,))?)?; fields.set_item("help", help)?; - let arguments: Bound<'_, PyDict> = PyDict::new(py); - arguments.set_item("key", PyTuple::new(py, key)?)?; - arguments.set_item( - "diagnostic", - new_record(py, RecordId::CompilerDiagnostic, (), Some(&fields))?, + report_compile_diagnostic( + PyTuple::new(py, key)?.as_any(), + &new_record(py, RecordId::CompilerDiagnostic, (), Some(&fields))?, )?; - report.call((), Some(&arguments))?; } Ok(()) } 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 9fab1e770d..4798701f02 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_errors.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_errors.rs @@ -1,9 +1,9 @@ //! Report a scope index's error findings as individual compile diagnostics. +use crate::bindings::_helpers::compile_driver::diagnostic_collector::report_compile_diagnostic; use crate::bindings::_helpers::records::construct::new_record; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::ids::RecordId; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyString, PyTuple}; @@ -12,7 +12,6 @@ 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 = registry::native(py, NativeAttr::ReportCompileDiagnostic)?; let path_class = py.import("pathlib")?.getattr("Path")?; for item in native_field(index, "diagnostics")?.try_iter()? { let item: Bound<'_, PyAny> = item?; @@ -55,10 +54,7 @@ pub(crate) fn report_scope_index_errors(index: &Bound<'_, PyAny>) -> PyResult<() fields.set_item("column", native_field(&item, "column")?)?; let diagnostic: Bound<'_, PyAny> = new_record(py, RecordId::CompilerDiagnostic, (), Some(&fields))?; - let arguments: Bound<'_, PyDict> = PyDict::new(py); - arguments.set_item("key", key)?; - arguments.set_item("diagnostic", diagnostic)?; - report.call((), Some(&arguments))?; + report_compile_diagnostic(key.as_any(), &diagnostic)?; } Ok(()) } 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 22b2ff067d..edba303dd2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sqltext/sql_references.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sqltext/sql_references.rs @@ -3,7 +3,7 @@ use pyo3::prelude::{ Bound, PyAny, PyAnyMethods, PyErr, PyModule, PyModuleMethods, PyResult, Python, }; -use pyo3::types::{PyDict, PyDictMethods, PyTuple}; +use pyo3::types::PyTuple; use pyo3::{IntoPyObject, IntoPyObjectExt, pyclass, pyfunction, pymethods, wrap_pyfunction}; use sqlbuild_sqltext::sql_references::main::extract_sql_references::extract_sql_references; use sqlbuild_sqltext::sql_references::models::{ @@ -12,11 +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::diagnostic_collector::report_compile_diagnostic; use crate::bindings::_helpers::records::construct::{ enum_from_value, enum_member, new_record_with, }; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sqltext::lexical_syntax::LexicalSyntaxInput; @@ -185,7 +186,6 @@ 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> = registry::native(py, NativeAttr::ReportCompileDiagnostic)?; for call in invalid_calls { let diagnostic: Bound<'py, PyAny> = new_record_with( py, @@ -204,13 +204,12 @@ pub(crate) fn extract_references<'py>( ("help", native_field(&call, "help")?), ], )?; - let keywords: Bound<'py, PyDict> = PyDict::new(py); - keywords.set_item( - "key", - (REFERENCE_CALL_SYNTAX_CODE, "", native_field(&call, "call")?), + report_compile_diagnostic( + (REFERENCE_CALL_SYNTAX_CODE, "", native_field(&call, "call")?) + .into_pyobject(py)? + .as_any(), + &diagnostic, )?; - keywords.set_item("diagnostic", diagnostic)?; - report.call((), Some(&keywords))?; } PyTuple::new(py, references) } From 5f1e12f24e3460de41326307ff11bd95a5ddc822 Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Sat, 17 Oct 2026 03:25:25 +0100 Subject: [PATCH 09/28] refactor: construct declaration contexts and binding positions directly --- .../bindings/_helpers/analysis/positions.rs | 2 +- .../_helpers/render/declaration_values.rs | 18 ++++++++++-------- .../_helpers/scopes/declaration_contexts.rs | 8 ++++---- .../semantic_shapes/source_bindings.rs | 6 +++--- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/analysis/positions.rs b/crates/sqlbuild-python/src/bindings/_helpers/analysis/positions.rs index 40b440c5bb..740a7b2e4e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/analysis/positions.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/analysis/positions.rs @@ -9,7 +9,7 @@ use sqlbuild_analysis::semantic_validation::models as validation; #[pymethods] impl BindingPositions { #[new] - fn new(request: PositionInput) -> PyResult { + pub(crate) fn new(request: PositionInput) -> PyResult { compiler_guard(|| { validation::BindingPositions::new(request.into()) .map(|inner| Self { inner }) 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 b4b83a67bb..da0bb7688c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/render/declaration_values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/render/declaration_values.rs @@ -15,6 +15,7 @@ use crate::bindings::_helpers::records::construct::{ use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::scopes::declaration_contexts::NativeDeclarationContexts; use crate::bindings::_helpers::scopes::scope_lookup::build_scope_lookup; const NO_DECLARATION_KIND: u8 = 255; @@ -310,16 +311,17 @@ pub(crate) fn native_declaration_contexts<'py>( .try_iter()? .collect::>>()?, )?; - registry::native(py, NativeAttr::NativeDeclarationContexts)?.call1(( - PyList::new(py, positions)?, + let contexts: NativeDeclarationContexts = NativeDeclarationContexts::new( + PyList::new(py, positions)?.extract()?, ( - global_positions, - listed_positions(&native_field(&index, "local_positions")?)?, - listed_positions(&native_field(&index, "inherited_positions")?)?, + global_positions.extract()?, + listed_positions(&native_field(&index, "local_positions")?)?.extract()?, + listed_positions(&native_field(&index, "inherited_positions")?)?.extract()?, ), - classes, - (keys, lookups), - )) + classes.extract()?, + (keys.extract()?, lookups.extract()?), + ); + Ok(Bound::new(py, contexts)?.into_any()) } /// Hand one scope lookup's positions and runtime values to the native classifier. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scopes/declaration_contexts.rs b/crates/sqlbuild-python/src/bindings/_helpers/scopes/declaration_contexts.rs index 293558c237..8dd5605aa1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/declaration_contexts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/declaration_contexts.rs @@ -17,7 +17,7 @@ const ENUM_KIND: u8 = 0; const CONSTANT_KIND: u8 = 1; const MACRO_KIND: u8 = 2; -type TableRow = ( +pub(crate) type TableRow = ( Vec, HashMap>, HashMap>, @@ -26,7 +26,7 @@ type VisibleRow<'py> = (Bound<'py, PyAny>, VisibleEntry, Option, Py, Py, u8, Py, u8, u32); +pub(crate) struct PositionRow(Py, Py, Py, u8, Py, u8, u32); /// The Python classes and enum members the contexts are built from. #[derive(FromPyObject)] @@ -50,7 +50,7 @@ pub(crate) struct NativeDeclarationContexts { /// The Python lookup mappings a matching resource's private positions and grants come from. #[derive(FromPyObject)] #[pyo3(from_item_all)] -struct LookupMappings { +pub(crate) struct LookupMappings { identity_keys: Py, private_positions: Py, grants_by_resource: Py, @@ -69,7 +69,7 @@ struct ContextParts<'py> { #[pymethods] impl NativeDeclarationContexts { #[new] - fn new( + pub(crate) fn new( positions: Vec, table: TableRow, classes: ContextClasses, 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 02de9f6925..c9638c5cba 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 @@ -19,13 +19,13 @@ use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::compile_driver::stage_work::count_stage; use crate::bindings::_helpers::records::construct::{enum_from_value, enum_member, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::semantic_shapes::constants::SOURCE_REBINDING_ANALYSES; use crate::bindings::_helpers::semantic_shapes::declarations::{declared_types, function_names}; use crate::bindings::_helpers::semantic_shapes::shapes::{ model_inputs, project_shapes, published_shape, }; +use crate::bindings::models::BindingPositions; const BINDING_UNKNOWN_TABLE_INTERNAL_CODE: &str = "B000"; const DATACLASSES_MODULE: &str = "dataclasses"; @@ -394,7 +394,7 @@ fn positions<'py>( request.set_item("expanded", expanded)?; request.set_item("cleaned", cleaned)?; request.set_item("passes", passes)?; - registry::native(py, NativeAttr::BindingPositions)?.call1((request,)) + Ok(Bound::new(py, BindingPositions::new(request.extract()?)?)?.into_any()) } /// Whether inspected columns differ from the known shape; untyped names match any type. From 820d0504c69512ba4563c8d50be213fdcfe95ae1 Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Sat, 17 Oct 2026 03:44:14 +0100 Subject: [PATCH 10/28] =?UTF-8?q?refactor:=20read=20own=20records=20native?= =?UTF-8?q?ly=20and=20rebuild=20them=20without=20dataclasses.replace=20in?= =?UTF-8?q?=20m=E2=80=93z=20bindings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Chained and dynamic getattr reads in the macro_bridge … workspace_commands bindings go through records::fields::field, and dataclasses.replace calls go through records::fields::replaced / replaced_keywords, which rebuild a native record from its own values. --- .../_helpers/macro_bridge/store_keys.rs | 10 +- .../src/bindings/_helpers/manifest/rows.rs | 2 +- .../_helpers/microbatches/projection.rs | 2 +- .../_helpers/microbatches/statements.rs | 6 +- .../_helpers/model_config/project_defaults.rs | 3 +- .../_helpers/output_capture/records.rs | 2 +- .../bindings/_helpers/plan_command/plan.rs | 2 +- .../bindings/_helpers/plan_command/rules.rs | 10 +- .../_helpers/plan_output/cursor_details.rs | 5 +- .../bindings/_helpers/plan_output/display.rs | 5 +- .../_helpers/plan_output/migrations.rs | 15 +-- .../_helpers/plan_output/plan_json.rs | 16 +-- .../_helpers/plan_output/plan_text.rs | 33 ++++--- .../bindings/_helpers/plan_output/safety.rs | 4 +- .../bindings/_helpers/plan_output/values.rs | 2 +- .../_helpers/planner_changes/detect.rs | 44 ++++----- .../_helpers/planner_changes/renames.rs | 7 +- .../_helpers/planner_entries/clone_plans.rs | 24 ++--- .../_helpers/planner_entries/command_plans.rs | 7 +- .../_helpers/planner_entries/display_plan.rs | 9 +- .../planner_entries/execution_plan.rs | 4 +- .../_helpers/planner_entries/model_entries.rs | 2 +- .../_helpers/planner_entries/plan_output.rs | 27 ++---- .../_helpers/planner_entries/values.rs | 6 +- .../_helpers/planner_identity/graph.rs | 5 +- .../_helpers/planner_identity/identities.rs | 35 +++---- .../_helpers/planner_identity/metadata.rs | 24 +++-- .../_helpers/planner_identity/staleness.rs | 6 +- .../planner_migrations/column_candidates.rs | 13 +-- .../planner_migrations/column_entries.rs | 20 ++-- .../planner_migrations/column_objects.rs | 17 ++-- .../planner_migrations/column_overlay.rs | 20 ++-- .../planner_migrations/compatibility.rs | 4 +- .../_helpers/planner_migrations/discovery.rs | 4 +- .../planner_migrations/discovery_events.rs | 14 +-- .../planner_migrations/discovery_models.rs | 19 ++-- .../planner_migrations/discovery_requests.rs | 13 +-- .../planner_migrations/fingerprint.rs | 5 +- .../planner_migrations/model_entries.rs | 42 +++----- .../planner_migrations/model_handover.rs | 4 +- .../planner_migrations/model_overlay.rs | 26 ++--- .../planner_migrations/model_planning.rs | 13 +-- .../planner_migrations/old_name_entries.rs | 22 ++--- .../planner_migrations/old_name_objects.rs | 9 +- .../planner_migrations/old_name_planning.rs | 21 ++-- .../planner_migrations/old_name_references.rs | 12 +-- .../planner_migrations/planning_boundary.rs | 2 +- .../planner_migrations/planning_execution.rs | 10 +- .../planner_migrations/planning_origin.rs | 24 ++--- .../planner_migrations/state_inspection.rs | 3 +- .../planner_phases/execution_changes.rs | 20 ++-- .../_helpers/planner_phases/full_refresh.rs | 8 +- .../_helpers/planner_phases/identities.rs | 13 +-- .../_helpers/planner_phases/objects.rs | 17 ++-- .../planner_phases/output_assembly.rs | 23 ++--- .../_helpers/planner_phases/retention.rs | 97 +++++++++---------- .../planner_phases/warehouse_state.rs | 20 ++-- .../planner_resolution/cursor_bounds.rs | 2 +- .../planner_resolution/cursor_model.rs | 13 +-- .../planner_resolution/cursor_overrides.rs | 4 +- .../planner_resolution/cursor_policies.rs | 2 +- .../planner_resolution/cursor_ranges.rs | 21 ++-- .../planner_resolution/cursor_safety.rs | 10 +- .../planner_resolution/cursor_warnings.rs | 4 +- .../planner_resolution/cursor_windows.rs | 30 ++---- .../planner_resolution/future_safety.rs | 2 +- .../planner_resolution/maximum_safety.rs | 2 +- .../planner_resolution/orchestration.rs | 20 ++-- .../_helpers/planner_resolution/sources.rs | 6 +- .../_helpers/planner_resolution/values.rs | 5 +- .../_helpers/planner_scenario/artifacts.rs | 8 +- .../bindings/_helpers/planner_scenario/cli.rs | 8 +- .../_helpers/planner_scenario/entries.rs | 8 +- .../_helpers/planner_scenario/execution.rs | 9 +- .../_helpers/planner_scenario/fixtures.rs | 2 +- .../_helpers/planner_scenario/graph.rs | 7 +- .../_helpers/planner_scenario/relations.rs | 12 +-- .../bindings/_helpers/planner_scenario/sql.rs | 2 +- .../_helpers/planner_scope/loaders.rs | 4 +- .../bindings/_helpers/planner_scope/scope.rs | 5 +- .../_helpers/planner_scope/sources.rs | 5 +- .../planner_selection/model_selectors.rs | 13 +-- .../_helpers/planner_selection/unit_tests.rs | 6 +- .../planner_strategy/audit_entries.rs | 8 +- .../planner_strategy/cursor_inputs.rs | 7 +- .../planner_strategy/fixture_context.rs | 4 +- .../planner_strategy/fixture_metadata.rs | 4 +- .../planner_strategy/fixture_reads.rs | 5 +- .../_helpers/planner_strategy/lineage.rs | 8 +- .../planner_strategy/microbatch_limits.rs | 9 +- .../planner_strategy/model_entries.rs | 24 ++--- .../_helpers/planner_strategy/values.rs | 13 +-- .../_helpers/planner_warehouse/cursors.rs | 15 +-- .../planner_warehouse/semantic_sources.rs | 12 +-- .../_helpers/planner_warehouse/snapshot.rs | 37 +++---- .../planner_warehouse/source_columns.rs | 6 +- .../planner_warehouse/source_deferral.rs | 19 ++-- .../planner_warehouse/source_freshness.rs | 10 +- .../planner_warehouse/source_tables.rs | 3 +- .../_helpers/planner_warehouse/watermarks.rs | 7 +- .../project_assembly/declared_relations.rs | 9 +- .../project_assembly/native_resources.rs | 22 ++--- .../project_assembly/syntax_checks.rs | 15 ++- .../bindings/_helpers/query_command/dag.rs | 3 +- .../bindings/_helpers/query_command/query.rs | 6 +- .../_helpers/record_behaviours/compiled.rs | 14 +-- .../_helpers/record_behaviours/cursors.rs | 4 +- .../_helpers/record_behaviours/discovery.rs | 11 +-- .../_helpers/record_behaviours/durations.rs | 3 +- .../_helpers/record_behaviours/error_inits.rs | 2 +- .../_helpers/record_behaviours/freshness.rs | 5 +- .../_helpers/record_behaviours/lexical.rs | 2 +- .../record_behaviours/lineage_facts.rs | 8 +- .../_helpers/record_behaviours/macros.rs | 28 ++---- .../_helpers/record_behaviours/migrations.rs | 4 +- .../_helpers/record_behaviours/planner.rs | 7 +- .../_helpers/record_behaviours/rule_shapes.rs | 6 +- .../_helpers/record_behaviours/scopes.rs | 2 +- .../_helpers/record_behaviours/values.rs | 8 +- .../src/bindings/_helpers/records/fields.rs | 13 ++- .../_helpers/refactor/contract_compare.rs | 17 +--- .../_helpers/refactor/contract_edits.rs | 12 +-- .../_helpers/refactor/offline_compile.rs | 2 +- .../_helpers/refactor/python_strings.rs | 2 +- .../_helpers/refactor/refactor_command.rs | 6 +- .../_helpers/refactor/refactor_facts.rs | 31 +++--- .../bindings/_helpers/references/relations.rs | 6 +- .../bindings/_helpers/render/authored_sql.rs | 4 +- .../_helpers/render/declaration_indexes.rs | 9 +- .../_helpers/render/declaration_values.rs | 26 +++-- .../_helpers/render/macro_call_classes.rs | 19 +--- .../_helpers/render/macro_evaluation.rs | 19 ++-- .../_helpers/render/macro_expansion.rs | 14 +-- .../_helpers/render/macro_expansion_result.rs | 5 +- .../render/macro_generated_references.rs | 4 +- .../bindings/_helpers/result_output/assets.rs | 27 +++--- .../bindings/_helpers/result_output/checks.rs | 10 +- .../_helpers/result_output/documents.rs | 16 +-- .../result_output/integration_records.rs | 15 +-- .../_helpers/result_output/projection.rs | 10 +- .../_helpers/result_output/resource_ids.rs | 2 +- .../_helpers/result_output/terminal_index.rs | 2 +- .../_helpers/rules/custom_evaluation.rs | 10 +- .../bindings/_helpers/rules/custom_hosts.rs | 5 +- .../src/bindings/_helpers/rules/findings.rs | 3 +- .../_helpers/rules/fixture_formatting.rs | 7 +- .../bindings/_helpers/rules/lint_inputs.rs | 3 +- .../bindings/_helpers/rules/relation_keys.rs | 3 +- .../src/bindings/_helpers/rules/rule_api.rs | 3 +- .../src/bindings/_helpers/rules/rule_cache.rs | 3 +- .../src/bindings/_helpers/rules/sql_rules.rs | 7 +- .../scenario_command/capture_command.rs | 14 ++- .../_helpers/scenario_command/conversions.rs | 2 +- .../_helpers/scenario_command/setup.rs | 23 ++--- .../scenario_command/snapshot_sync.rs | 15 ++- .../_helpers/scenario_command/test_command.rs | 20 ++-- .../_helpers/scopes/compile_usages.rs | 19 ++-- .../_helpers/scopes/declaration_resolution.rs | 5 +- .../_helpers/scopes/relationship_names.rs | 4 +- .../bindings/_helpers/scopes/scope_command.rs | 7 +- .../bindings/_helpers/scopes/scope_errors.rs | 2 +- .../bindings/_helpers/scopes/scope_index.rs | 10 +- .../bindings/_helpers/scopes/scope_objects.rs | 10 +- .../_helpers/scopes/scope_report_objects.rs | 13 +-- .../bindings/_helpers/scopes/scope_reports.rs | 14 +-- .../_helpers/scopes/scope_tolerant.rs | 2 +- .../_helpers/scopes/scope_validation.rs | 30 +++--- .../bindings/_helpers/seed_command/command.rs | 29 +++--- .../_helpers/semantic_checks/completion.rs | 2 +- .../_helpers/semantic_checks/inputs.rs | 6 +- .../_helpers/semantic_checks/metadata.rs | 30 ++---- .../_helpers/semantic_checks/resource_sql.rs | 28 +++--- .../_helpers/semantic_shapes/coverage.rs | 13 +-- .../_helpers/semantic_shapes/shapes.rs | 29 ++---- .../semantic_shapes/source_bindings.rs | 28 ++---- .../_helpers/sql_test_command/test_command.rs | 41 ++++---- .../_helpers/sql_test_glue/assembly.rs | 33 +++---- .../_helpers/sql_test_glue/authored_ctes.rs | 3 +- .../_helpers/sql_test_glue/compiled_tests.rs | 13 +-- .../_helpers/sql_test_glue/cursor_window.rs | 8 +- .../sql_test_glue/fixture_validation.rs | 3 +- .../_helpers/sql_test_glue/plan_driver.rs | 23 +++-- .../_helpers/sql_test_glue/plan_entries.rs | 27 +++--- .../_helpers/sql_test_glue/plan_errors.rs | 9 +- .../_helpers/sql_test_glue/plan_fixtures.rs | 11 +-- .../_helpers/sql_test_glue/plan_rendering.rs | 6 +- .../_helpers/sql_test_glue/planning.rs | 9 +- .../_helpers/state_rows/migration_io.rs | 6 +- .../state_rows/migration_projections.rs | 4 +- .../_helpers/state_rows/migration_rows.rs | 12 +-- .../_helpers/state_rows/state_tables.rs | 5 +- 191 files changed, 952 insertions(+), 1323 deletions(-) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/macro_bridge/store_keys.rs b/crates/sqlbuild-python/src/bindings/_helpers/macro_bridge/store_keys.rs index 3fbbcce94a..6ff716cb59 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/macro_bridge/store_keys.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/macro_bridge/store_keys.rs @@ -150,7 +150,7 @@ impl<'py> CanonicalTypes<'py> { let mut items: Vec = vec![JsonValue::String("timedelta".to_owned())]; for part in ["days", "seconds", "microseconds"] { items.push(JsonValue::Integer(JsonInteger::from( - value.getattr(part)?.extract::()?, + native_field(value, part)?.extract::()?, ))); } return Ok(JsonValue::Array(items)); @@ -233,8 +233,7 @@ pub(crate) fn macro_store_token<'py>( let types: CanonicalTypes<'py> = CanonicalTypes::load(py)?; let name: JsonValue = text(native_field(&loaded, "name")?.downcast::()?)?; let path: JsonValue = text( - loaded - .getattr("relative_path")? + native_field(&loaded, "relative_path")? .call_method0("as_posix")? .downcast::()?, )?; @@ -275,7 +274,10 @@ pub(crate) fn context_store_token<'py>( ]; for name in ["inaccessible_constants", "inaccessible_enums"] { let mut items: Vec = Vec::new(); - for item in sorted.call1((declarations.getattr(name)?,))?.try_iter()? { + for item in sorted + .call1((native_field(&declarations, name)?,))? + .try_iter()? + { items.push(plain_json(&item?)?); } parts.push(JsonValue::Array(items)); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/manifest/rows.rs b/crates/sqlbuild-python/src/bindings/_helpers/manifest/rows.rs index f878efbcc5..fac146af2d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/manifest/rows.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/manifest/rows.rs @@ -22,7 +22,7 @@ const MACRO_KIND: &str = "macro"; const DEFAULT_MATERIALIZATION: &str = "view"; fn attribute<'py>(value: &Bound<'py, PyAny>, name: &str) -> PyResult> { - value.getattr(name) + native_field(value, name) } fn text(value: &Bound<'_, PyAny>, name: &str) -> PyResult { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/microbatches/projection.rs b/crates/sqlbuild-python/src/bindings/_helpers/microbatches/projection.rs index fe5c70c1bd..1e904e48dd 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/microbatches/projection.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/microbatches/projection.rs @@ -187,7 +187,7 @@ fn replay<'py>(request: Option<&Bound<'py, PyDict>>) -> PyResult = piece?; pieces.push(CoveragePiece { interval: parser.interval(&piece)?, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/microbatches/statements.rs b/crates/sqlbuild-python/src/bindings/_helpers/microbatches/statements.rs index ea9060f12e..cd90df39ac 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/microbatches/statements.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/microbatches/statements.rs @@ -261,9 +261,9 @@ fn create_indexes<'py>(request: Option<&Bound<'py, PyDict>>) -> PyResult>) -> PyResult { let request = Request::new("deterministic_microbatch_event_id", request)?; let scope: Bound<'_, PyAny> = request.get("scope")?; - let field = |name: &str| -> PyResult { scope.getattr(name)?.extract() }; + let field = |name: &str| -> PyResult { native_field(&scope, name)?.extract() }; let optional = - |name: &str| -> PyResult> { optional_text(&scope.getattr(name)?) }; + |name: &str| -> PyResult> { optional_text(&native_field(&scope, name)?) }; let identity = MicrobatchEventIdentity { scope_kind: field("scope_kind")?, scope_key: field("scope_key")?, @@ -272,7 +272,7 @@ fn deterministic_event_id(request: Option<&Bound<'_, PyDict>>) -> PyResult( ) -> PyResult> { let values: Bound<'py, PyDict> = PyDict::new(defaults.py()); for (key, falsy_unset) in DEFAULT_KEYS { - let value: Bound<'py, PyAny> = defaults.getattr(key)?; + let value: Bound<'py, PyAny> = native_field(defaults, key)?; let unset: bool = if falsy_unset { !value.is_truthy()? } else { 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 8249be09a4..e2edf0a83a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/output_capture/records.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/output_capture/records.rs @@ -136,7 +136,7 @@ pub(crate) fn freeze_command_output_record<'py>( pub(crate) fn command_output_to_json(record: &Bound<'_, PyAny>) -> PyResult { let field = |name: &str| record.getattr(name); let occurred_at: String = call_with( - &field("occurred_at")?.getattr("isoformat")?, + &native_field(&field("occurred_at")?, "isoformat")?, &[("timespec", object(record.py(), "microseconds")?)], )? .extract()?; 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 beb7074893..05a5ceb660 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/plan_command/plan.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/plan_command/plan.rs @@ -208,7 +208,7 @@ fn compile_pipeline<'py>( }, )?; for field in PIPELINE_FIELDS { - options.set_item(field, request.getattr(field)?)?; + options.set_item(field, native_field(request, field)?)?; } options.set_item("auto_load_sources", &invocation.load_sources)?; options.set_item( 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 0b24ec7cba..d3265ca2f2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/plan_command/rules.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/plan_command/rules.rs @@ -16,6 +16,7 @@ use crate::bindings::_helpers::planner_selection::selectors::select_project_grap use crate::bindings::_helpers::planner_selection::unit_tests::reject_unit_test_selectors; use crate::bindings::_helpers::records::construct::new_record; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced_keywords; use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::rules::catalogue::build_rule_catalogue; use crate::bindings::_helpers::rules::guidance::{format_rule, install_rules_skills}; @@ -100,7 +101,7 @@ fn list_rules(catalogue: &Bound<'_, PyAny>, json_output: bool) -> PyResult let rule: Bound<'_, PyAny> = rule?; let item: Bound<'_, PyDict> = PyDict::new(py); for field in ["code", "family", "message", "custom"] { - item.set_item(field, rule.getattr(field)?)?; + item.set_item(field, native_field(&rule, field)?)?; } let subject: Bound<'_, PyAny> = native_field(&rule, "subject")?; item.set_item( @@ -224,10 +225,7 @@ fn run_rule_selection( &[ ("graph", graph), ("discovered_inputs", discovered), - ( - "config", - imported(py, "dataclasses", "replace")?.call((config,), Some(&replaced))?, - ), + ("config", replaced_keywords(config, &replaced)?), ("project_dir", project_dir.clone()), ( "dialect", @@ -255,7 +253,7 @@ fn run_rule_selection( } let fields: Bound<'_, PyDict> = PyDict::new(py); for field in RESULT_FIELDS { - fields.set_item(field, result.getattr(field)?)?; + fields.set_item(field, native_field(&result, field)?)?; } let text: String = format_rules_result( &new_record(py, RecordId::RulesResult, (), Some(&fields))?, 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 707e8109b6..e66e8d319b 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 @@ -60,10 +60,7 @@ fn effective_grain(entry: &Bound<'_, PyAny>) -> PyResult> { return Ok(declared); } let mut effective: String = declared.unwrap_or_else(|| SECOND_GRAIN.to_owned()); - if entry - .getattr("microbatch_strategy")? - .eq(WATERMARK_STRATEGY)? - { + if native_field(entry, "microbatch_strategy")?.eq(WATERMARK_STRATEGY)? { return Ok(Some(effective)); } for relation in native_field(entry, "cursor_input_relations")?.try_iter()? { 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 43043a0c2d..d6cccac33f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/plan_output/display.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/plan_output/display.rs @@ -32,7 +32,8 @@ use crate::bindings::_helpers::records::ids::RecordId; const DATETIME_MODULE: &str = "datetime"; fn label_facts(entry: &Bound<'_, PyAny>) -> PyResult { - let field = |name: &str| -> PyResult> { optional_text(&entry.getattr(name)?) }; + let field = + |name: &str| -> PyResult> { optional_text(&native_field(entry, name)?) }; Ok(ModelLabelFacts { materialization_type: text(&native_field(entry, "materialization_type")?)?, action: text(&native_field(entry, "action")?)?, @@ -129,7 +130,7 @@ fn parsed_bound(value: &Bound<'_, PyAny>) -> PyResult> { }; let mut midnight: bool = true; for part in ["hour", "minute", "second", "microsecond"] { - midnight = midnight && parsed.getattr(part)?.extract::()? == 0; + midnight = midnight && native_field(&parsed, part)?.extract::()? == 0; } Ok(Some(ParsedBound { date: parsed diff --git a/crates/sqlbuild-python/src/bindings/_helpers/plan_output/migrations.rs b/crates/sqlbuild-python/src/bindings/_helpers/plan_output/migrations.rs index 9b5ec90e53..f1e6b2d95e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/plan_output/migrations.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/plan_output/migrations.rs @@ -46,8 +46,11 @@ fn migration(entry: &Bound<'_, PyAny>, materialization: Option) -> PyRes origin: text(&relation_name(&native_field(entry, "origin")?)?)?, destination: text(&relation_name(&native_field(entry, "destination")?)?)?, decision, - compatibility: text(&native_field(entry, "compatibility")?.getattr("value")?)?, - discovery: text(&native_field(entry, "discovery")?.getattr("value")?)?, + compatibility: text(&native_field( + &native_field(entry, "compatibility")?, + "value", + )?)?, + discovery: text(&native_field(&native_field(entry, "discovery")?, "value")?)?, completed_at: optional_text(&iso(&native_field(entry, "completed_at")?)?)?, target_label: text(&native_field(entry, "target_label")?)?, transfer: label(&native_field(entry, "transfer")?)?, @@ -85,7 +88,7 @@ fn column(entry: &Bound<'_, PyAny>) -> PyResult { origin_column: text(&native_field(entry, "origin_column")?)?, destination_column: text(&native_field(entry, "destination_column")?)?, decision: decision(entry)?, - discovery: text(&native_field(entry, "discovery")?.getattr("value")?)?, + discovery: text(&native_field(&native_field(entry, "discovery")?, "value")?)?, completed_at: optional_text(&iso(&native_field(entry, "completed_at")?)?)?, }) } @@ -161,7 +164,7 @@ pub(crate) fn pre_build_lines( let request = native_field(&entry, "request")?; let mut parts: Vec = Vec::new(); for field in ["database", "schema", "name"] { - let part = request.getattr(field)?; + let part = native_field(&request, field)?; if part.is_truthy()? { parts.push(text(&part)?); } @@ -172,8 +175,8 @@ pub(crate) fn pre_build_lines( actual_days: days(&native_field(&entry, "actual_days")?)?, effective_days: days(&native_field(&entry, "effective_days")?)?, source: text(&native_field(&entry, "source")?)?, - direction: text(&native_field(&entry, "direction")?.getattr("value")?)?, - phase: text(&native_field(&entry, "phase")?.getattr("value")?)?, + direction: text(&native_field(&native_field(&entry, "direction")?, "value")?)?, + phase: text(&native_field(&native_field(&entry, "phase")?, "value")?)?, irreversible_warning: optional_text(&native_field(&entry, "irreversible_warning")?)?, }); } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/plan_output/plan_json.rs b/crates/sqlbuild-python/src/bindings/_helpers/plan_output/plan_json.rs index eea3606772..64a3add7ec 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/plan_output/plan_json.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/plan_output/plan_json.rs @@ -102,7 +102,7 @@ fn identity_status(entry: &Bound<'_, PyAny>) -> PyResult<&'static str> { fn evidence<'py>(entry: &Bound<'py, PyAny>, field: &str) -> PyResult> { let microbatch_range: Bound<'py, PyAny> = native_field(entry, "microbatch_range")?; if !microbatch_range.is_none() { - let found: Bound<'py, PyAny> = microbatch_range.getattr(field)?; + let found: Bound<'py, PyAny> = native_field(µbatch_range, field)?; if !found.is_none() { return Ok(found); } @@ -111,7 +111,7 @@ fn evidence<'py>(entry: &Bound<'py, PyAny>, field: &str) -> PyResult(entry: &Bound<'py, PyAny>) -> PyResult> { @@ -176,7 +176,7 @@ fn model_json<'py>(entry: &Bound<'py, PyAny>) -> PyResult> { ], )?; for key in ["incremental_strategy", "incremental_mode"] { - let value: Bound<'py, PyAny> = entry.getattr(key)?; + let value: Bound<'py, PyAny> = native_field(entry, key)?; if !value.is_none() { model.set_item(key, value)?; } @@ -185,7 +185,7 @@ fn model_json<'py>(entry: &Bound<'py, PyAny>) -> PyResult> { model.set_item("microbatch_state", microbatch_state(entry)?)?; } for key in ["cursor_column", "cursor_type"] { - let value: Bound<'py, PyAny> = entry.getattr(key)?; + let value: Bound<'py, PyAny> = native_field(entry, key)?; if !value.is_none() { model.set_item(key, value)?; } @@ -227,7 +227,7 @@ fn model_json<'py>(entry: &Bound<'py, PyAny>) -> PyResult> { model.set_item("changed_functions", list(&changed_functions)?)?; } let qualified: Bound<'py, PyAny> = - native_field(entry, "destination")?.getattr("qualified_name")?; + native_field(&native_field(entry, "destination")?, "qualified_name")?; if !qualified.is_none() { model.set_item("qualified_name", qualified)?; } @@ -260,7 +260,7 @@ fn with_qualified_name<'py>( entry: &Bound<'py, PyAny>, ) -> PyResult> { let qualified: Bound<'py, PyAny> = - native_field(entry, "destination")?.getattr("qualified_name")?; + native_field(&native_field(entry, "destination")?, "qualified_name")?; if !qualified.is_none() { item.set_item("qualified_name", qualified)?; } @@ -362,7 +362,7 @@ fn warning_json<'py>(warning: &Bound<'py, PyAny>) -> PyResult> ], )?; for key in ["model_name", "code"] { - let value: Bound<'py, PyAny> = warning.getattr(key)?; + let value: Bound<'py, PyAny> = native_field(warning, key)?; if !value.is_none() { item.set_item(key, value)?; } @@ -722,7 +722,7 @@ pub(crate) fn format_plan_json( } else { Vec::new() }; - let length = |field: &str| -> PyResult { plan.getattr(field)?.len() }; + let length = |field: &str| -> PyResult { native_field(&plan, field)?.len() }; let python_nodes: Vec> = python_entries .iter() .map(python_entry_json) 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 8272aa1032..282a8e1553 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 @@ -169,8 +169,8 @@ fn config_diff(entry: &Bound<'_, PyAny>, reason: &str) -> PyResult) -> PyResult { - let action: String = text(&native_field(entry, "action")?.getattr("value")?)?; - let reason: String = text(&native_field(entry, "reason")?.getattr("value")?)?; + let action: String = text(&native_field(&native_field(entry, "action")?, "value")?)?; + let reason: String = text(&native_field(&native_field(entry, "reason")?, "value")?)?; let name: String = text(&native_field(entry, "name")?)?; if action == SKIP_ACTION { return Ok(ModelPlanText { @@ -184,7 +184,7 @@ fn model_text(entry: &Bound<'_, PyAny>) -> PyResult { let mut schema_findings: Vec = Vec::new(); for finding in items(&native_field(entry, "schema_findings")?)? { schema_findings.push(SchemaFindingText { - kind: text(&native_field(&finding, "kind")?.getattr("value")?)?, + kind: text(&native_field(&native_field(&finding, "kind")?, "value")?)?, column_name: text(&native_field(&finding, "column_name")?)?, expected_type: optional_text(&native_field(&finding, "expected_type")?)?, actual_type: optional_text(&native_field(&finding, "actual_type")?)?, @@ -214,7 +214,7 @@ fn model_text(entry: &Bound<'_, PyAny>) -> PyResult { entry, "custom_materialization_name", )?)?, - backfill_action: text(&native_field(&backfill, "action")?.getattr("value")?)?, + backfill_action: text(&native_field(&native_field(&backfill, "action")?, "value")?)?, backfill_duration: optional_text(&native_field(&backfill, "duration")?)?, cursor: cursor_details(entry)? .map(|details| cursor_text(entry, details)) @@ -231,7 +231,7 @@ fn model_text(entry: &Bound<'_, PyAny>) -> PyResult { } fn function_text(entry: &Bound<'_, PyAny>) -> PyResult { - let reason: String = text(&native_field(entry, "reason")?.getattr("value")?)?; + let reason: String = text(&native_field(&native_field(entry, "reason")?, "value")?)?; let previous: Bound<'_, PyAny> = native_field(entry, "previous_query_sql")?; let query_diff_lines: Option> = if reason == QUERY_CHANGED && !previous.is_none() { Some(query_diff( @@ -248,7 +248,7 @@ fn function_text(entry: &Bound<'_, PyAny>) -> PyResult { } else { format!( "{} udf", - text(&native_field(entry, "language")?.getattr("value")?)? + text(&native_field(&native_field(entry, "language")?, "value")?)? ) }, reason, @@ -260,7 +260,10 @@ fn source_load_text(entry: &Bound<'_, PyAny>) -> PyResult { let strategy: Bound<'_, PyAny> = native_field(entry, "write_strategy")?; Ok(SourceLoadPlanText { name: text(&native_field(entry, "name")?)?, - resource_kind: text(&native_field(entry, "resource_kind")?.getattr("value")?)?, + resource_kind: text(&native_field( + &native_field(entry, "resource_kind")?, + "value", + )?)?, is_reload: native_field(entry, "is_reload")?.is_truthy()?, integration_kind: optional_text(&native_field(entry, "integration_kind")?)?, write_strategy: if strategy.is_none() { @@ -275,7 +278,10 @@ fn source_load_text(entry: &Bound<'_, PyAny>) -> PyResult { /// Both identity diffs of a changed Python entry, each as `format_query_diff` lines. fn python_text(entry: &Bound<'_, PyAny>) -> PyResult { - let identity_status: String = text(&native_field(entry, "identity_status")?.getattr("value")?)?; + let identity_status: String = text(&native_field( + &native_field(entry, "identity_status")?, + "value", + )?)?; let mut diffs: [Vec; 2] = [Vec::new(), Vec::new()]; if identity_status == CHANGED_IDENTITY { let sources = [ @@ -303,8 +309,8 @@ fn python_text(entry: &Bound<'_, PyAny>) -> PyResult { let [source_diff, dependency_diff] = diffs; Ok(PythonPlanText { name: text(&native_field(entry, "name")?)?, - kind: text(&native_field(entry, "kind")?.getattr("value")?)?, - phase: text(&native_field(entry, "phase")?.getattr("value")?)?, + kind: text(&native_field(&native_field(entry, "kind")?, "value")?)?, + phase: text(&native_field(&native_field(entry, "phase")?, "value")?)?, identity_status, source_diff, dependency_diff, @@ -327,7 +333,7 @@ fn provider_usages( } for entry in python_entries { let (kind, name) = ( - text(&native_field(entry, "kind")?.getattr("value")?)?, + text(&native_field(&native_field(entry, "kind")?, "value")?)?, text(&native_field(entry, "name")?)?, ); for usage in items(&native_field(entry, "provider_usages")?)? { @@ -437,7 +443,10 @@ fn report(arguments: &KeywordArguments<'_>) -> PyResult { let mut warnings: Vec = Vec::new(); for warning in items(&native_field(&plan, "warnings")?)? { warnings.push(PlanWarningText { - severity: text(&native_field(&warning, "severity")?.getattr("value")?)?, + severity: text(&native_field( + &native_field(&warning, "severity")?, + "value", + )?)?, message: text(&native_field(&warning, "message")?)?, model_name: optional_text(&native_field(&warning, "model_name")?)?, }); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/plan_output/safety.rs b/crates/sqlbuild-python/src/bindings/_helpers/plan_output/safety.rs index 733d559750..4a89d40637 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/plan_output/safety.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/plan_output/safety.rs @@ -28,8 +28,8 @@ fn bounds<'py>( Ok(dict( evidence.py(), vec![ - ("start", render(evidence.getattr(start)?)?), - ("end", render(evidence.getattr(end)?)?), + ("start", render(native_field(evidence, start)?)?), + ("end", render(native_field(evidence, end)?)?), ], )? .into_any()) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/plan_output/values.rs b/crates/sqlbuild-python/src/bindings/_helpers/plan_output/values.rs index 2c3b6725f5..67981b9d20 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/plan_output/values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/plan_output/values.rs @@ -33,7 +33,7 @@ pub(crate) fn enum_value<'py>( value: &Bound<'py, PyAny>, name: &str, ) -> PyResult> { - native_field(&value.getattr(name)?, "value") + native_field(&native_field(value, name)?, "value") } /// `value.value` when `value` is set, else `None`. 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 1dd367fdf9..8cccda9fca 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/detect.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/detect.rs @@ -102,11 +102,9 @@ pub(crate) fn detect_changes<'py>( let functions: Bound<'py, PyDict> = PyDict::new(py); let selected: Bound<'py, PyAny> = native_field(scope, "selected_keys")?; let recorded: Bound<'py, PyAny> = - native_field(snapshot, "fingerprints")?.getattr("functions")?; - let query_change_tracking: bool = project - .getattr("settings")? - .getattr("query_change_tracking")? - .is_truthy()?; + native_field(&native_field(snapshot, "fingerprints")?, "functions")?; + let query_change_tracking: bool = + native_field(&native_field(project, "settings")?, "query_change_tracking")?.is_truthy()?; let dialect: Option = native_field(snapshot, "column_dialect")?.extract()?; for function in native_field(project, "functions")?.try_iter()? { let function: Bound<'py, PyAny> = function?; @@ -134,9 +132,7 @@ pub(crate) fn detect_changes<'py>( .extract::()?, dialect.as_deref(), ), - )? != fingerprint - .getattr("definition_hash")? - .extract::()? + )? != native_field(&fingerprint, "definition_hash")?.extract::()? { "query_changed" } else { @@ -246,15 +242,13 @@ pub(crate) fn scope_model_changes<'py>( let py: Python<'py> = project.py(); let tracking: bool = match query_change_tracking { Some(value) => value, - None => project - .getattr("settings")? - .getattr("query_change_tracking")? - .extract()?, + None => { + native_field(&native_field(project, "settings")?, "query_change_tracking")?.extract()? + } }; let function_local_hashes: Bound<'py, PyAny> = function_local_hashes( &native_field(project, "functions")?, - project - .getattr("sql_analysis_dialect")? + native_field(project, "sql_analysis_dialect")? .extract::>()? .as_deref(), )? @@ -340,13 +334,11 @@ pub(crate) fn model_change<'py>( "no_change", ); } - let relation_exists: bool = snapshot - .getattr("existing_relations")? - .contains(&model_name)?; - let fingerprint: Bound<'py, PyAny> = snapshot - .getattr("fingerprints")? - .getattr("models")? - .call_method1("get", (&model_name,))?; + let relation_exists: bool = + native_field(snapshot, "existing_relations")?.contains(&model_name)?; + let fingerprint: Bound<'py, PyAny> = + native_field(&native_field(snapshot, "fingerprints")?, "models")? + .call_method1("get", (&model_name,))?; let recorded: Option = recorded_fingerprint(&fingerprint)?; let dialect: Bound<'py, PyAny> = native_field(snapshot, "column_dialect")?; let compiled_query_hash: Option = if pass.query_change_tracking && recorded.is_some() { @@ -446,7 +438,7 @@ pub(crate) fn model_change<'py>( fn replay_policy(model: &Bound<'_, PyAny>) -> PyResult { let py: Python<'_> = model.py(); let raw: Option = authored_config_str( - &native_field(model, "config")?.getattr("values")?, + &native_field(&native_field(model, "config")?, "values")?, REPLAY_CONFIG_KEY, )? .map(|value| value.extract()) @@ -533,9 +525,8 @@ fn model_schema_findings<'py>( model_name: &Bound<'py, PyAny>, dialect: &Bound<'py, PyAny>, ) -> PyResult> { - let warehouse: Bound<'py, PyAny> = snapshot - .getattr("existing_columns")? - .call_method1("get", (model_name,))?; + let warehouse: Bound<'py, PyAny> = + native_field(snapshot, "existing_columns")?.call_method1("get", (model_name,))?; if warehouse.is_none() { return Ok(Vec::new()); } @@ -550,8 +541,7 @@ fn model_schema_findings<'py>( yml.push((native_field(&column, "name")?.extract()?, kind)); } } - type_enforcement = schema_entry - .getattr("type_enforcement")? + type_enforcement = native_field(&schema_entry, "type_enforcement")? .extract::>()? .unwrap_or(false); let families: Bound<'py, PyAny> = native_field(&schema_entry, "dynamic_columns")?; 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 b53ef7a4bd..17d88ea590 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/renames.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_changes/renames.rs @@ -179,9 +179,8 @@ pub(crate) fn plan_reference_renames<'py>( &native_field(&state, "inspect_migration_events")?, &[("schemas", inspected.into_any())], )?; - let target_name: Bound<'py, PyAny> = runtime - .getattr("project")? - .getattr("effective_target_name")?; + let target_name: Bound<'py, PyAny> = + native_field(&native_field(runtime, "project")?, "effective_target_name")?; let mut renames: Vec> = Vec::new(); for item in planned.call_method0("items")?.try_iter()? { let item: Bound<'py, PyAny> = item?; @@ -232,7 +231,7 @@ fn changed_referencing_query( } } let recorded_models: Bound<'_, PyAny> = - native_field(snapshot, "fingerprints")?.getattr("models")?; + native_field(&native_field(snapshot, "fingerprints")?, "models")?; let ref_kind: Bound<'_, PyAny> = enum_member(py, RecordId::SqlReferenceKind, "REF")?; let dialect: Option = native_field(snapshot, "column_dialect")?.extract()?; for model in selected { 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 4ba7000e0a..933511f7f0 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 @@ -36,9 +36,7 @@ const GRAPH_ARGUMENTS: [&str; 4] = ["selected", "upstream", "is_clonable", "is_v /// Whether a model is explicitly disabled with a boolean `enabled = false`. fn disabled_model(model: &Bound<'_, PyAny>) -> PyResult { - let raw = model - .getattr("config")? - .getattr("values")? + let raw = native_field(&native_field(model, "config")?, "values")? .call_method1("get", ("enabled",))?; Ok(raw.is_instance_of::() && !raw.is_truthy()?) } @@ -72,9 +70,7 @@ fn clone_plan_output<'py>( let selected = object( py, resolve_graph_selection( - project - .getattr("lineage_graph")? - .extract::>()?, + native_field(project, "lineage_graph")?.extract::>()?, select, exclude, )?, @@ -337,7 +333,7 @@ fn clone_function_entries<'py>( ], )?), )?; - let field = |name: &str| function.getattr(name); + let field = |name: &str| native_field(&function, name); let entry = planner_model( py, RecordId::FunctionPlanEntry, @@ -378,12 +374,12 @@ fn seed_entry<'py>(seed: &Bound<'py, PyAny>) -> PyResult> { ("destination", native_field(seed, "destination")?), ( "file_path", - native_field(seed, "seed_file")?.getattr("file_path")?, + native_field(&native_field(seed, "seed_file")?, "file_path")?, ), ("columns", PyTuple::empty(py).into_any()), ( "csv_settings", - native_field(seed, "schema_entry")?.getattr("csv_settings")?, + native_field(&native_field(seed, "schema_entry")?, "csv_settings")?, ), ], ) @@ -398,7 +394,7 @@ fn seed_entries<'py>( let mut entries: Vec> = Vec::new(); for seed in native_field(project, "seeds")?.try_iter()? { let seed = seed?; - if wanted.contains(seed.getattr(field)?)? { + if wanted.contains(native_field(&seed, field)?)? { entries.push(seed_entry(&seed)?); } } @@ -497,8 +493,7 @@ fn run_clone_planning<'py>( &destination_sources, )?; let mut sources = PyTuple::empty(py).into_any(); - if destination_sources - .getattr("effective_target_name")? + if native_field(&destination_sources, "effective_target_name")? .eq(native_field(&project, "effective_target_name")?)? { sources = clone_source_entries( @@ -530,10 +525,7 @@ fn run_clone_planning<'py>( let Some(origin) = origin_by_name.get_item(native_field(&entry, "name")?)? else { continue; }; - if origin - .getattr("destination")? - .ne(native_field(&entry, "destination")?)? - { + if native_field(&origin, "destination")?.ne(native_field(&entry, "destination")?)? { kept_origins.push(origin); kept_sources.push(entry); } 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 fcced5ca07..be2b1736f1 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 @@ -320,12 +320,12 @@ pub(crate) fn build_seed_command_plan<'py>( ("destination", native_field(&seed, "destination")?), ( "file_path", - native_field(&seed, "seed_file")?.getattr("file_path")?, + native_field(&native_field(&seed, "seed_file")?, "file_path")?, ), ("columns", object(py, extract_seed_columns(&seed)?)?), ( "csv_settings", - native_field(&seed, "schema_entry")?.getattr("csv_settings")?, + native_field(&native_field(&seed, "schema_entry")?, "csv_settings")?, ), ("fingerprint_definition", metadata_json.clone()), ("fingerprint_version_hash", version_hash), @@ -436,8 +436,7 @@ pub(crate) fn read_selected_seed_fingerprints<'py>( )?))?; let items = PyList::new( py, - latest - .getattr("fingerprints")? + native_field(&latest, "fingerprints")? .call_method0("items")? .try_iter()? .collect::>>()?, 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 a7cbf6ed29..c29a356f48 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 @@ -41,10 +41,11 @@ fn display_action( } fn config<'py>(model: &Bound<'py, PyAny>, key: &str) -> PyResult> { - Ok( - authored_config_str(&native_field(model, "config")?.getattr("values")?, key)? - .unwrap_or_else(|| model.py().None().into_bound(model.py())), - ) + Ok(authored_config_str( + &native_field(&native_field(model, "config")?, "values")?, + key, + )? + .unwrap_or_else(|| model.py().None().into_bound(model.py()))) } fn display_entry<'py>( 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 c0acf2c362..61289d856c 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 @@ -234,9 +234,7 @@ pub(crate) fn build_execution_plan<'py>( selected_keys: native_field(&inspection, "selected_keys")?.unbind(), sql_test_selection: native_field(&inspection, "sql_test_selection")?.unbind(), }, - args.value("policies")? - .getattr("plan_sql_tests")? - .is_truthy()?, + native_field(&args.value("policies")?, "plan_sql_tests")?.is_truthy()?, )?; let planned = (|| { let catalog = call_with( 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 08e014e64f..2f32727daf 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 @@ -266,7 +266,7 @@ fn check_binding_diagnostics<'py>( let mut lines: Vec = Vec::new(); for diagnostic in &errors { let position = |field: &str| -> PyResult { - let value = diagnostic.getattr(field)?; + let value = native_field(diagnostic, field)?; Ok(if value.is_truthy()? { value.str()?.to_string() } else { 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 0be95249c6..ea11b26fed 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 @@ -76,23 +76,16 @@ fn seed_plan_reason<'py>( if !reason.is_none() { return Ok(reason); } - if !snapshot - .getattr("existing_relations")? - .contains(seed_name)? - { + if !native_field(snapshot, "existing_relations")?.contains(seed_name)? { return planner_type(py, RecordId::PlanReason, "FIRST_RUN"); } - let fingerprint = snapshot - .getattr("fingerprints")? - .getattr("seeds")? + let fingerprint = native_field(&native_field(snapshot, "fingerprints")?, "seeds")? .call_method1("get", (seed_name,))?; if fingerprint.is_none() { return planner_type(py, RecordId::PlanReason, "FIRST_RUN"); } if !expected_version_hash.is_none() - && fingerprint - .getattr("version_hash")? - .ne(expected_version_hash)? + && native_field(&fingerprint, "version_hash")?.ne(expected_version_hash)? { return planner_type(py, RecordId::PlanReason, "CONFIG_CHANGED"); } @@ -125,12 +118,12 @@ fn seed_entries<'py>( ("destination", native_field(&seed, "destination")?), ( "file_path", - native_field(&seed, "seed_file")?.getattr("file_path")?, + native_field(&native_field(&seed, "seed_file")?, "file_path")?, ), ("columns", object(py, extract_seed_columns(&seed)?)?), ( "csv_settings", - native_field(&seed, "schema_entry")?.getattr("csv_settings")?, + native_field(&native_field(&seed, "schema_entry")?, "csv_settings")?, ), ( "fingerprint_definition", @@ -199,7 +192,7 @@ fn hook_provider_usages<'py>( let python_hook = record_class(py, RecordId::PythonHookEntry)?; let mut hooks: Vec> = Vec::new(); for field in ["pre_hooks", "post_hooks"] { - let hook_entry = entry.getattr(field)?; + let hook_entry = native_field(entry, field)?; if hook_entry.is_none() { continue; } @@ -374,7 +367,7 @@ pub(super) fn function_entry<'py>( ], )?), )?; - let field = |name: &str| function.getattr(name); + let field = |name: &str| native_field(function, name); planner_model( py, RecordId::FunctionPlanEntry, @@ -408,7 +401,7 @@ fn function_entries<'py>( ) -> PyResult>> { let py = project.py(); let functions = native_field(changes, "functions")?; - let previous = native_field(snapshot, "fingerprints")?.getattr("functions")?; + let previous = native_field(&native_field(snapshot, "fingerprints")?, "functions")?; let mut entries: Vec> = Vec::new(); for function in native_field(project, "functions")?.try_iter()? { let function = function?; @@ -485,7 +478,7 @@ pub(super) fn selected_test_entries<'py>( if selected.is_empty() { return Ok(SelectedTests { entries, warnings }); } - let analysis = native_field(project, "settings")?.getattr("sql_analysis")?; + let analysis = native_field(&native_field(project, "settings")?, "sql_analysis")?; let fixture_context = if analysis.is_truthy()? { Bound::new(py, planning_context(project, None)?)?.into_any() } else { @@ -694,7 +687,7 @@ pub(crate) fn plan_output_fields<'py>( ), ( "python_identity_fingerprints", - native_field(&snapshot, "fingerprints")?.getattr("python_nodes")?, + native_field(&native_field(&snapshot, "fingerprints")?, "python_nodes")?, ), ]) } 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 0ef05e4c46..78f9af0c81 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/values.rs @@ -1,7 +1,8 @@ //! 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::compile_driver::python_calls::{call_with, keywords}; use crate::bindings::_helpers::records::construct::{enum_member, new_record_with}; +use crate::bindings::_helpers::records::fields::replaced_keywords; use crate::bindings::_helpers::records::ids::{ErrorId, NativeItem, RecordId}; use crate::bindings::_helpers::records::registry; use pyo3::IntoPyObject; @@ -58,8 +59,7 @@ pub(super) fn replaced<'py>( object: &Bound<'py, PyAny>, fields: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - imported(object.py(), "dataclasses", "replace")? - .call((object,), Some(&keywords(object.py(), fields)?)) + replaced_keywords(object, &keywords(object.py(), fields)?) } /// `class(message, **fields)` from `module`, as a raised 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 b21c2a67af..4d2c2da06c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/graph.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/graph.rs @@ -88,14 +88,13 @@ pub(crate) fn build_execution_upstream_deps<'py>( let py: Python<'py> = project.py(); let upstream: Bound<'py, PyDict> = PyDict::new(py); for collection in ["models", "sources", "seeds", "functions"] { - for resource in project.getattr(collection)?.try_iter()? { + for resource in native_field(project, collection)?.try_iter()? { let resource: Bound<'py, PyAny> = resource?; upstream.set_item( native_field(&resource, "key")?, PyList::new( py, - resource - .getattr("deps")? + native_field(&resource, "deps")? .try_iter()? .collect::>>()?, )?, 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 faa82435df..3829a4699b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/identities.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/identities.rs @@ -15,7 +15,7 @@ use sqlbuild_analysis::planner_identity::main::model_local_identity_hash::model_ use sqlbuild_analysis::planner_identity::models::{GraphKey, IdentityGraph, IdentityNode}; use crate::bindings::_helpers::analysis::model_reads; -use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; 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}; @@ -24,10 +24,9 @@ use crate::bindings::_helpers::planner_identity::metadata::{ }; use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced; use crate::bindings::_helpers::records::ids::RecordId; -const DATACLASSES_MODULE: &str = "dataclasses"; - fn key_of(kind: &str, name: &str) -> GraphKey { GraphKey { kind: kind.to_owned(), @@ -236,8 +235,7 @@ pub(crate) fn with_honest_model_write_hashes<'py>( let mut graph = IdentityGraph::default(); let mut base_hashes: Vec<(GraphKey, String)> = Vec::new(); let mut function_deps: HashMap> = HashMap::new(); - for item in scope - .getattr("upstream_deps")? + for item in native_field(&scope, "upstream_deps")? .call_method0("items")? .try_iter()? { @@ -264,7 +262,7 @@ pub(crate) fn with_honest_model_write_hashes<'py>( } } let recorded_seeds: Bound<'py, PyAny> = - native_field(&snapshot, "fingerprints")?.getattr("seeds")?; + native_field(&native_field(&snapshot, "fingerprints")?, "seeds")?; for (name, seed_hash) in string_pairs(&native_field(&identities, "seed_version_hashes")?)? { let seed_key: GraphKey = key_of(SEED_RESOURCE, &name); graph.nodes.insert( @@ -283,8 +281,7 @@ pub(crate) fn with_honest_model_write_hashes<'py>( base_hashes.push((seed_key, write_hash)); } let local_hashes: Bound<'py, PyAny> = native_field(&identities, "model_local_hashes")?; - for model in scope - .getattr("models_by_name")? + for model in native_field(&scope, "models_by_name")? .call_method0("values")? .try_iter()? { @@ -299,7 +296,7 @@ pub(crate) fn with_honest_model_write_hashes<'py>( ); } let recorded_models: Bound<'py, PyAny> = - native_field(&snapshot, "fingerprints")?.getattr("models")?; + native_field(&native_field(&snapshot, "fingerprints")?, "models")?; for item in recorded_models.call_method0("items")?.try_iter()? { let (name, fingerprint): (String, Bound<'py, PyAny>) = item?.extract()?; let version_hash: Bound<'py, PyAny> = native_field(&fingerprint, "version_hash")?; @@ -318,11 +315,9 @@ pub(crate) fn with_honest_model_write_hashes<'py>( graph_write_identity_hashes(&graph, &selected, base_hashes) .into_iter() .collect(); - let replace: Bound<'py, PyAny> = imported(py, DATACLASSES_MODULE, "replace")?; let model_changes: Bound<'py, PyDict> = PyDict::new(py); model_changes.update( - changes - .getattr("models")? + native_field(&changes, "models")? .downcast::()? .as_mapping(), )?; @@ -352,7 +347,6 @@ pub(crate) fn with_honest_model_write_hashes<'py>( model_changes.set_item( &name, replace_with( - &replace, &change, ( "fingerprint_version_hash", @@ -361,7 +355,7 @@ pub(crate) fn with_honest_model_write_hashes<'py>( )?, )?; } - replace_with(&replace, &changes, ("models", model_changes.into_any())) + replace_with(&changes, ("models", model_changes.into_any())) } /// Resolved actions with recomputed model changes patched in where available. @@ -372,11 +366,9 @@ pub(crate) fn merge_recomputed_model_changes<'py>( changes: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = resolved_actions.py(); - let replace: Bound<'py, PyAny> = imported(py, DATACLASSES_MODULE, "replace")?; let recomputed: Bound<'py, PyAny> = native_field(changes, "models")?; let merged: Bound<'py, PyDict> = PyDict::new(py); - for item in resolved_actions - .getattr("models")? + for item in native_field(resolved_actions, "models")? .call_method0("items")? .try_iter()? { @@ -385,19 +377,16 @@ pub(crate) fn merge_recomputed_model_changes<'py>( if change.is_none() { change = native_field(&resolved, "change")?; } - merged.set_item(name, replace_with(&replace, &resolved, ("change", change))?)?; + merged.set_item(name, replace_with(&resolved, ("change", change))?)?; } - replace_with(&replace, resolved_actions, ("models", merged.into_any())) + replace_with(resolved_actions, ("models", merged.into_any())) } fn replace_with<'py>( - replace: &Bound<'py, PyAny>, instance: &Bound<'py, PyAny>, (field, value): (&str, Bound<'py, PyAny>), ) -> PyResult> { - let keywords: Bound<'py, PyDict> = PyDict::new(replace.py()); - keywords.set_item(field, value)?; - replace.call((instance,), Some(&keywords)) + replaced(instance, &[(field, value)]) } pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/metadata.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/metadata.rs index f830a766ec..50cb22e106 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/metadata.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/metadata.rs @@ -102,7 +102,7 @@ pub(crate) fn model_version_identity_metadata_json<'py>( } } } - let config_values: Bound<'py, PyAny> = native_field(model, "config")?.getattr("values")?; + let config_values: Bound<'py, PyAny> = native_field(&native_field(model, "config")?, "values")?; let signature: Bound<'py, PyDict> = execution_signature(model, &config_values, hook_version_hashes)?; version_identity_metadata_json( @@ -217,7 +217,7 @@ fn contract_output_signature<'py>( fn column_signature<'py>(column: &Bound<'py, PyAny>) -> PyResult> { let entry: Bound<'py, PyDict> = PyDict::new(column.py()); for field in ["name", "type", "nullable"] { - entry.set_item(field, column.getattr(field)?)?; + entry.set_item(field, native_field(column, field)?)?; } Ok(entry) } @@ -235,7 +235,7 @@ fn family_signatures<'py>(families: &Bound<'py, PyAny>) -> PyResult) -> PyResult< let payload: Bound<'_, PyDict> = PyDict::new(py); payload.set_item( "contract", - model - .getattr("config")? - .getattr("values")? + native_field(&native_field(model, "config")?, "values")? .call_method1("get", (CONTRACT_CONFIG_KEY,))?, )?; let columns: Bound<'_, PyList> = PyList::empty(py); @@ -284,7 +282,8 @@ pub(crate) fn function_local_hashes<'py>( for function in functions.try_iter()? { let function: Bound<'py, PyAny> = function?; let name: String = native_field(&function, "name")?.extract()?; - let language: Bound<'py, PyAny> = native_field(&function, "language")?.getattr("value")?; + let language: Bound<'py, PyAny> = + native_field(&native_field(&function, "language")?, "value")?; let body_hash: String = function_definition_hash( py, &name, @@ -307,7 +306,7 @@ pub(crate) fn function_local_hashes<'py>( payload.set_item("body_sql", body_hash)?; payload.set_item("language", language)?; for field in ["runtime_version", "entry_point", "packages"] { - payload.set_item(field, function.getattr(field)?)?; + payload.set_item(field, native_field(&function, field)?)?; } hashes.set_item( name, @@ -331,7 +330,7 @@ fn name_type_pairs<'py>(items: &Bound<'py, PyAny>) -> PyResult, name: &str) -> PyResult { - let value: Bound<'_, PyAny> = match owner.getattr(name) { + let value: Bound<'_, PyAny> = match native_field(owner, name) { Ok(value) => value, Err(_) => return Ok(String::new()), }; @@ -361,8 +360,7 @@ pub(crate) fn compiled_function_fingerprint_sql(function: &Bound<'_, PyAny>) -> return_columns: rendered_pairs(&native_field(function, "return_columns")?)?, runtime_version: text_or_empty(function, "runtime_version")?, entry_point: text_or_empty(function, "entry_point")?, - packages: function - .getattr("packages")? + packages: native_field(function, "packages")? .try_iter()? .map(|package| package?.extract::()) .collect::>()?, @@ -383,7 +381,7 @@ pub(crate) fn seed_identity(seed: &Bound<'_, PyAny>) -> PyResult<(String, String PyList::empty(py).into_any() } else { seed_rows( - &native_field(seed, "seed_file")?.getattr("file_path")?, + &native_field(&native_field(seed, "seed_file")?, "file_path")?, &csv_settings, )? }; @@ -411,7 +409,7 @@ fn seed_rows<'py>( }; let options: Bound<'py, PyDict> = PyDict::new(py); for option in SEED_DIALECT_OPTIONS { - let value: Bound<'py, PyAny> = csv_settings.getattr(option)?; + let value: Bound<'py, PyAny> = native_field(csv_settings, option)?; if !value.is_none() { options.set_item(option, value)?; } 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 c9c2988faf..4e703fefba 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/staleness.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_identity/staleness.rs @@ -123,11 +123,9 @@ fn changed_model_names(inputs: &StalenessInputs<'_>) -> PyResult fn changed_seed_names(inputs: &StalenessInputs<'_>) -> PyResult> { let recorded: Bound<'_, PyAny> = - native_field(&inputs.snapshot, "fingerprints")?.getattr("seeds")?; + native_field(&native_field(&inputs.snapshot, "fingerprints")?, "seeds")?; let mut changed: HashSet = HashSet::new(); - for item in inputs - .identities - .getattr("seed_version_hashes")? + for item in native_field(&inputs.identities, "seed_version_hashes")? .call_method0("items")? .try_iter()? { 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 c439eae2a3..73f6fa81c0 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 @@ -27,8 +27,7 @@ pub(super) fn candidates<'py>( { continue; } - let model = scope - .getattr("models_by_name")? + let model = native_field(&scope, "models_by_name")? .call_method1("get", (native_field(&key, "name")?,))?; if model.is_none() { continue; @@ -43,8 +42,7 @@ pub(super) fn candidates<'py>( { continue; } - let warehouse = snapshot - .getattr("existing_columns")? + let warehouse = native_field(&snapshot, "existing_columns")? .call_method1("get", (&name, PyTuple::empty(py)))?; let declared = PyDict::new(py); let schema = native_field(&model, "schema_entry")?; @@ -60,13 +58,10 @@ pub(super) fn candidates<'py>( } } } - let fingerprint = snapshot - .getattr("fingerprints")? - .getattr("models")? + let fingerprint = native_field(&native_field(&snapshot, "fingerprints")?, "models")? .call_method1("get", (&name,))?; let changed = !fingerprint.is_none() - && !fingerprint - .getattr("definition_hash")? + && !native_field(&fingerprint, "definition_hash")? .eq(definition_hash(&model, &snapshot)?)?; if !warehouse.is_truthy()? || (declared.is_empty() && !changed) { continue; 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 f9343537a6..3a165d5625 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 @@ -126,9 +126,7 @@ pub(super) fn decide<'py>( if done { native_field(&newest, "target_name")? } else { - runtime - .getattr("project")? - .getattr("effective_target_name")? + native_field(&native_field(runtime, "project")?, "effective_target_name")? }, ), ( @@ -154,9 +152,7 @@ pub(super) fn checked<'py>( let py = runtime.py(); let mut pending = false; for entry in &entries { - pending |= native_field(entry, "decision")? - .getattr("renames")? - .is_truthy()?; + pending |= native_field(&native_field(entry, "decision")?, "renames")?.is_truthy()?; } if !pending { return Ok(CheckedColumns { @@ -174,9 +170,10 @@ pub(super) fn checked<'py>( native_field(&destination, "name")? }; let reason = call_with( - &runtime - .getattr("adapter")? - .getattr("column_rename_unavailable_reason")?, + &native_field( + &native_field(runtime, "adapter")?, + "column_rename_unavailable_reason", + )?, &[ ( "connection", @@ -194,10 +191,7 @@ pub(super) fn checked<'py>( let mut checked: Vec> = Vec::new(); let mut warnings: Vec> = Vec::new(); for entry in entries { - if !native_field(&entry, "decision")? - .getattr("renames")? - .is_truthy()? - { + if !native_field(&native_field(&entry, "decision")?, "renames")?.is_truthy()? { checked.push(entry); } else if native_field(&entry, "discovery")?.eq("manual")? { checked.push(replace( 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 b7bd7438f6..a3ed28aa03 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,12 +1,13 @@ //! Boundary conversions for the column migration planner. use crate::bindings::_helpers::analysis::model_reads; -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::model_config::config_values::config_str as authored_config_str; use crate::bindings::_helpers::planner_identity::hashing::model_definition_hash; use crate::bindings::_helpers::planner_phases::objects::object; use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced_keywords; use crate::bindings::_helpers::records::ids::{NativeItem, RecordId}; use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; @@ -28,7 +29,7 @@ pub(super) fn replace<'py>( for (name, field) in fields { kwargs.set_item(name, field)?; } - imported(value.py(), "dataclasses", "replace")?.call((value,), Some(&kwargs)) + replaced_keywords(value, &kwargs) } pub(super) fn model<'py>( @@ -48,10 +49,11 @@ pub(super) fn native<'py>( } pub(super) fn config<'py>(model: &Bound<'py, PyAny>, key: &str) -> PyResult> { - Ok( - authored_config_str(&native_field(model, "config")?.getattr("values")?, key)? - .unwrap_or_else(|| model.py().None().into_bound(model.py())), - ) + Ok(authored_config_str( + &native_field(&native_field(model, "config")?, "values")?, + key, + )? + .unwrap_or_else(|| model.py().None().into_bound(model.py()))) } pub(super) fn definition_hash<'py>( @@ -64,8 +66,7 @@ pub(super) fn definition_hash<'py>( model.py(), &model_reads::name_object(model)?.extract::()?, &model_reads::query_sql(model)?.extract::()?, - snapshot - .getattr("column_dialect")? + native_field(snapshot, "column_dialect")? .extract::>()? .as_deref(), )?, 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 b05e2eaf77..fd474377f4 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 @@ -47,10 +47,7 @@ pub(super) fn overlay<'py>( native_field(entry, "origin_column")?, native_field(entry, "destination_column")?, )?; - if native_field(entry, "decision")? - .getattr("renames")? - .is_truthy()? - { + if native_field(&native_field(entry, "decision")?, "renames")?.is_truthy()? { pending.set_item( lower(&native_field(entry, "origin_column")?)?, native_field(entry, "destination_column")?, @@ -61,8 +58,7 @@ pub(super) fn overlay<'py>( continue; } let mut renamed: Vec> = Vec::new(); - for column in snapshot - .getattr("existing_columns")? + for column in native_field(snapshot, "existing_columns")? .call_method1("get", (&name, PyTuple::empty(py)))? .try_iter()? { @@ -145,9 +141,7 @@ pub(super) fn cursors<'py>( } else { py.None().into_bound(py) }; - if native_field(entry, "decision")? - .getattr("renames")? - .is_truthy()? + if native_field(&native_field(entry, "decision")?, "renames")?.is_truthy()? && !cursor.is_none() && lower(&cursor)? == lower(&native_field(entry, "destination_column")?)? { @@ -163,10 +157,10 @@ pub(super) fn cursors<'py>( let target = if physical.is_truthy()? { physical } else { - let qualified = models - .get_item(&name)? - .getattr("destination")? - .getattr("qualified_name")?; + let qualified = native_field( + &native_field(&models.get_item(&name)?, "destination")?, + "qualified_name", + )?; if qualified.is_truthy()? { qualified } else { 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 1e583e79fd..df77c45227 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/compatibility.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/compatibility.rs @@ -19,9 +19,7 @@ use sqlbuild_analysis::planner_migrations::main::compatibility_findings::compati use sqlbuild_analysis::planner_migrations::models::CompatibilityFinding; fn config(model: &Bound<'_, PyAny>, key: &str) -> PyResult> { - model - .getattr("config")? - .getattr("values")? + native_field(&native_field(model, "config")?, "values")? .call_method1("get", (key,))? .extract() } 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 5c70a6033a..b23a6cb872 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/discovery.rs @@ -46,9 +46,7 @@ pub(super) fn discover_model_migrations<'py>( let metadata = discovery_models::metadata(&runtime, &models)?; let inputs = FingerprintInputs { metadata_jsons: metadata.into_any(), - dialect: runtime - .getattr("adapter")? - .call_method0("sql_analysis_dialect")?, + dialect: native_field(&runtime, "adapter")?.call_method0("sql_analysis_dialect")?, fingerprints: cache, }; call_with( 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 6df7bfbf46..6732bc6c9e 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 @@ -67,12 +67,9 @@ pub(super) fn renames<'py>( state: &Bound<'py, PyAny>, ) -> PyResult> { let py = runtime.py(); - let target = runtime - .getattr("project")? - .getattr("effective_target_name")?; + let target = native_field(&native_field(runtime, "project")?, "effective_target_name")?; let ordered = PyList::empty(py); - let events: Vec> = state - .getattr("events")? + let events: Vec> = native_field(state, "events")? .try_iter()? .collect::>()?; for (index, event) in events.iter().enumerate() { @@ -109,9 +106,7 @@ pub(super) fn resumed<'py>( state: &Bound<'py, PyAny>, ) -> PyResult> { let result = PyDict::new(runtime.py()); - let target = runtime - .getattr("project")? - .getattr("effective_target_name")?; + let target = native_field(&native_field(runtime, "project")?, "effective_target_name")?; let events = native_field(state, "events")?; for item in models { let name = native_field(item, "name")?; @@ -124,8 +119,7 @@ pub(super) fn resumed<'py>( continue; } let event_target = native_field(&event, "target_name")?; - if event - .getattr("destination")? + if native_field(&event, "destination")? .call_method1("matches", (relation(&destination)?,))? .is_truthy()? && (event_target.is_none() || event_target.eq(&target)?) 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 fb01a8af4c..2e6597b9e7 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 @@ -17,9 +17,11 @@ use std::collections::BTreeMap; pub(super) fn ordered<'py>(runtime: &Bound<'py, PyAny>) -> PyResult>> { let mut models: BTreeMap> = BTreeMap::new(); let mut dependencies: BTreeMap> = BTreeMap::new(); - for item in runtime_field(runtime, |runtime| &runtime.project)? - .getattr("models")? - .try_iter()? + for item in native_field( + &runtime_field(runtime, |runtime| &runtime.project)?, + "models", + )? + .try_iter()? { let item = item?; let name: String = native_field(&item, "name")?.extract()?; @@ -46,20 +48,18 @@ pub(super) fn triggered( declared: &Bound<'_, PyDict>, ) -> PyResult { let py = scope.py(); - let selected: Vec> = scope - .getattr("selected_keys")? + let selected: Vec> = native_field(scope, "selected_keys")? .try_iter()? .collect::>()?; let mut relevant = PyNodeSet(PySet::new(py, &selected)?); breadth_first_reach(selected, None, &mut relevant, |key| { - scope - .getattr("upstream_deps")? + native_field(scope, "upstream_deps")? .call_method1("get", (key, pyo3::types::PyTuple::empty(py)))? .try_iter()? .collect::>>>() })?; let relevant = relevant.0; - let fingerprints = native_field(snapshot, "fingerprints")?.getattr("models")?; + let fingerprints = native_field(&native_field(snapshot, "fingerprints")?, "models")?; for item in models { let name = native_field(item, "name")?; if relevant.contains(native_field(item, "key")?)? @@ -82,8 +82,7 @@ pub(super) fn metadata<'py>( py, build_function_local_hashes( &native_field(&project, "functions")?, - project - .getattr("sql_analysis_dialect")? + native_field(&project, "sql_analysis_dialect")? .extract::>()? .as_deref(), )?, 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 07a436836b..bc9ed26749 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 @@ -93,8 +93,7 @@ pub(super) fn candidates<'py>( } let event = newest(&native_field(state, "events")?, &location)?; if !event.is_none() - && event - .getattr("origin")? + && native_field(&event, "origin")? .call_method1("matches", (relation(&location)?,))? .is_truthy()? { @@ -114,10 +113,7 @@ pub(super) fn automatic<'py>( let py = runtime.py(); let mut requests: Vec> = Vec::new(); for item in models { - if !scope - .getattr("selected_keys")? - .contains(native_field(item, "key")?)? - { + if !native_field(scope, "selected_keys")?.contains(native_field(item, "key")?)? { continue; } let name = native_field(item, "name")?; @@ -165,10 +161,7 @@ pub(super) fn warnings<'py>( let py = scope.py(); let selected = PySet::empty(py)?; for item in models { - if scope - .getattr("selected_keys")? - .contains(native_field(item, "key")?)? - { + if native_field(scope, "selected_keys")?.contains(native_field(item, "key")?)? { selected.add(native_field(item, "name")?)?; } } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/fingerprint.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/fingerprint.rs index b8e3e1006d..31e4eea1b3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/fingerprint.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/fingerprint.rs @@ -3,6 +3,7 @@ use crate::bindings::_helpers::analysis::model_reads; use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced_keywords; use pyo3::exceptions::PyValueError; use pyo3::prelude::{Bound, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyFrozenSet, PyTuple}; @@ -90,7 +91,7 @@ pub(crate) fn with_migration_fingerprints<'py>( py: Python<'py>, options: Option>, ) -> PyResult> { - use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; + use crate::bindings::_helpers::compile_driver::python_calls::call_with; let arguments = KeywordArguments::read( py, "with_migration_fingerprints", @@ -123,7 +124,7 @@ pub(crate) fn with_migration_fingerprints<'py>( )?; let kwargs = PyDict::new(py); kwargs.set_item("migration_fingerprint", fingerprint)?; - attached.push(imported(py, "dataclasses", "replace")?.call((entry,), Some(&kwargs))?); + attached.push(replaced_keywords(&entry, &kwargs)?); } cache.call_method0("persist")?; PyTuple::new(py, attached) 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 67a67d6934..7d74bf6b36 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 @@ -113,9 +113,7 @@ pub(super) fn decide<'py>( ("destination", destination.clone()), ( "target_name", - runtime - .getattr("project")? - .getattr("effective_target_name")?, + native_field(&native_field(&runtime, "project")?, "effective_target_name")?, ), ( "origin_version_hash", @@ -144,12 +142,10 @@ pub(super) fn decide<'py>( ], )?; let recorded = !newest.is_none() - && newest - .getattr("destination")? + && native_field(&newest, "destination")? .call_method1("matches", (relation(&destination)?,))? .is_truthy()? - && newest - .getattr("origin")? + && native_field(&newest, "origin")? .call_method1("matches", (relation(&origin)?,))? .is_truthy()?; if request.identity_only { @@ -162,9 +158,7 @@ pub(super) fn decide<'py>( ); } let inherited = if recorded && !applies(&item, &newest, &snapshot)? { - snapshot - .getattr("fingerprints")? - .getattr("models")? + native_field(&native_field(&snapshot, "fingerprints")?, "models")? .call_method1("get", (native_field(&item, "name")?,))? } else { empty_version(&item, &fingerprint)? @@ -194,9 +188,7 @@ pub(super) fn decide<'py>( &native_field(&newest, "origin_version_hash")?, )? } else { - snapshot - .getattr("fingerprints")? - .getattr("models")? + native_field(&native_field(&snapshot, "fingerprints")?, "models")? .call_method1("get", (native_field(&item, "name")?,))? }; return result( @@ -226,27 +218,22 @@ pub(super) fn decide<'py>( model: item.clone(), origin_relation, origin_columns: state.call_method1("relation_columns", (&origin,))?, - sql_analysis_enabled: runtime - .getattr("project")? - .getattr("settings")? - .getattr("sql_analysis")?, + sql_analysis_enabled: native_field( + &native_field(&native_field(&runtime, "project")?, "settings")?, + "sql_analysis", + )?, column_dialect: native_field(&snapshot, "column_dialect")?, snapshots_config: decision_request.snapshots_config.clone(), })?; let name = model_decision( - snapshot - .getattr("existing_relations")? - .contains(native_field(&item, "name")?)?, + native_field(&snapshot, "existing_relations")?.contains(native_field(&item, "name")?)?, ( !newest.is_none(), !newest.is_none() - && newest - .getattr("origin")? + && native_field(&newest, "origin")? .call_method1("matches", (relation(&destination)?,))? .is_truthy()?, - snapshot - .getattr("fingerprints")? - .getattr("models")? + native_field(&native_field(&snapshot, "fingerprints")?, "models")? .contains(native_field(&item, "name")?)?, ), request.force, @@ -258,10 +245,7 @@ pub(super) fn decide<'py>( ("compatibility_findings", compatibility.findings), ], )?; - if !entry - .getattr("decision")? - .getattr("moves_data")? - .is_truthy()? + if !native_field(&native_field(&entry, "decision")?, "moves_data")?.is_truthy()? || native_field(&entry, "blocks_build")?.is_truthy()? { return result(entry, py.None().into_bound(py), false); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/model_handover.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/model_handover.rs index 9d713cfc26..9f6f6ac829 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/model_handover.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_migrations/model_handover.rs @@ -16,9 +16,7 @@ pub(super) fn applies( event: &Bound<'_, PyAny>, snapshot: &Bound<'_, PyAny>, ) -> PyResult { - let own = snapshot - .getattr("fingerprints")? - .getattr("models")? + let own = native_field(&native_field(snapshot, "fingerprints")?, "models")? .call_method1("get", (model_reads::name_object(model)?,))?; Ok(own.is_none() || presumed_utc(&native_field(&own, "ts")?)? 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 60f7564254..f7eafc4b5a 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 @@ -21,17 +21,14 @@ pub(super) fn overlay<'py>( } let runtime = phase.runtime.clone(); let py = runtime.py(); - let relations = snapshot - .getattr("existing_relations")? + let relations = native_field(&snapshot, "existing_relations")? .cast_into::()? .copy()?; - let columns = snapshot - .getattr("existing_columns")? + let columns = native_field(&snapshot, "existing_columns")? .cast_into::()? .copy()?; let fingerprints = native_field(&snapshot, "fingerprints")?; - let models = fingerprints - .getattr("models")? + let models = native_field(&fingerprints, "models")? .cast_into::()? .copy()?; let redirected = PyDict::new(py); @@ -45,11 +42,7 @@ pub(super) fn overlay<'py>( } else { models.set_item(&name, handover)?; } - if !entry - .getattr("decision")? - .getattr("moves_data")? - .is_truthy()? - { + if !native_field(&native_field(entry, "decision")?, "moves_data")?.is_truthy()? { continue; } let origin = native_field(entry, "origin")?; @@ -84,8 +77,7 @@ pub(super) fn overlay<'py>( redirected.set_item(name, qualified)?; } } - let cursors = snapshot - .getattr("cursor_snapshots")? + let cursors = native_field(&snapshot, "cursor_snapshots")? .cast_into::()? .copy()?; let gathered = super::planning_boundary::redirected_cursor_snapshots( @@ -118,9 +110,11 @@ pub(super) fn overlay<'py>( } fn cursor_enabled(runtime: &Bound<'_, PyAny>, name: &Bound<'_, PyAny>) -> PyResult { - for item in runtime_field(runtime, |runtime| &runtime.project)? - .getattr("models")? - .try_iter()? + for item in native_field( + &runtime_field(runtime, |runtime| &runtime.project)?, + "models", + )? + .try_iter()? { let item = item?; if native_field(&item, "name")?.eq(name)? { 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 4716417459..8cc7381729 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 @@ -24,7 +24,7 @@ use pyo3::types::{ fn declared<'py>(item: &Bound<'py, PyAny>) -> PyResult>> { let py = item.py(); - let values = native_field(item, "config")?.getattr("values")?; + let values = native_field(&native_field(item, "config")?, "values")?; let origin = values.call_method1("get", ("migrate_from",))?; if !origin.is_instance_of::() { return Ok(None); @@ -54,8 +54,7 @@ fn manual_migration_requests<'py>( { continue; } - let item = scope - .getattr("models_by_name")? + let item = native_field(scope, "models_by_name")? .call_method1("get", (native_field(&key, "name")?,))?; if !item.is_none() && let Some(request) = declared(&item)? @@ -91,9 +90,11 @@ fn discover<'py>( let scope = phase.scope.clone(); let snapshot = phase.snapshot.clone(); let mut declared_requests: Vec> = Vec::new(); - for item in runtime_field(&runtime, |runtime| &runtime.project)? - .getattr("models")? - .try_iter()? + for item in native_field( + &runtime_field(&runtime, |runtime| &runtime.project)?, + "models", + )? + .try_iter()? { if let Some(request) = declared(&item?)? { declared_requests.push(request); 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 70cdd3f85d..c6c867664d 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 @@ -52,16 +52,14 @@ impl<'py> OldNamePlanner<'py> { let mut newest: Option<(Bound<'py, PyTuple>, Bound<'py, PyAny>)> = None; for history in &self.histories { let movement = native_field(history, "move")?; - if !movement - .getattr("destination")? + if !native_field(&movement, "destination")? .call_method1("matches", (destination,))? .is_truthy()? { continue; } if let Some(origin) = origin - && !movement - .getattr("origin")? + && !native_field(&movement, "origin")? .call_method1("matches", (origin,))? .is_truthy()? { @@ -119,9 +117,7 @@ impl<'py> OldNamePlanner<'py> { else { return Ok(None); }; - if !history - .getattr("move")? - .getattr("destination_model")? + if !native_field(&native_field(&history, "move")?, "destination_model")? .eq(native_field(item, "name")?)? { return Ok(None); @@ -277,7 +273,7 @@ impl<'py> OldNamePlanner<'py> { &[ ( "model_name", - native_field(history, "move")?.getattr("destination_model")?, + native_field(&native_field(history, "move")?, "destination_model")?, ), ( "origin", @@ -290,11 +286,11 @@ impl<'py> OldNamePlanner<'py> { ("action", action(&self.runtime, "NONE")?), ( "target_name", - native_field(history, "move")?.getattr("target_name")?, + native_field(&native_field(history, "move")?, "target_name")?, ), ( "migration_event_id", - native_field(history, "move")?.getattr("event_id")?, + native_field(&native_field(history, "move")?, "event_id")?, ), ("reason", reason), ], @@ -302,7 +298,7 @@ impl<'py> OldNamePlanner<'py> { .map(Some); } } - let retention = native_field(history, "required")?.getattr("view_retention")?; + let retention = native_field(&native_field(history, "required")?, "view_retention")?; let duration = record_class(py, RecordId::Duration)?.call_method1( "parse", (if retention.is_truthy()? { @@ -340,7 +336,7 @@ impl<'py> OldNamePlanner<'py> { ("action", action(&self.runtime, action_name)?), ( "target_name", - native_field(history, "move")?.getattr("target_name")?, + native_field(&native_field(history, "move")?, "target_name")?, ), ("retention", retention), ("expires_at", expires_at), @@ -358,7 +354,7 @@ impl<'py> OldNamePlanner<'py> { ), ( "migration_event_id", - native_field(history, "move")?.getattr("event_id")?, + native_field(&native_field(history, "move")?, "event_id")?, ), ( "grants_copied", 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 8a0ca29733..52b8e9dcca 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 @@ -24,9 +24,7 @@ pub(super) fn location<'py>( ("name", native_field(relation, "name")?), ]; let qualified = call_with( - &runtime - .getattr("adapter")? - .getattr("render_qualified_name")?, + &native_field(&native_field(runtime, "adapter")?, "render_qualified_name")?, &fields, )?; fields.push(("qualified_name", qualified)); @@ -42,7 +40,10 @@ pub(super) fn retention<'py>( item: &Bound<'py, PyAny>, project: &Bound<'py, PyAny>, ) -> PyResult> { - old_name_retention(&native_field(item, "config")?.getattr("values")?, project) + old_name_retention( + &native_field(&native_field(item, "config")?, "values")?, + project, + ) } pub(super) fn expires<'py>( 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 a79839ee77..e7967e5971 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 @@ -23,25 +23,25 @@ fn schemas<'py>( let snapshot = phase.snapshot.clone(); let needed = PySet::empty(py)?; for item in selected { - let schema = native_field(item, "destination")?.getattr("schema")?; + let schema = native_field(&native_field(item, "destination")?, "schema")?; if !schema.is_none() { needed.add(schema)?; } } for entry in migration_entries.try_iter()? { - let schema = native_field(&entry?, "origin")?.getattr("schema")?; + let schema = native_field(&native_field(&entry?, "origin")?, "schema")?; if !schema.is_none() { needed.add(schema)?; } } - let mut include_project = runtime - .getattr("project")? - .getattr("unmatched_literal_sql_relations")? - .is_truthy()?; + let mut include_project = native_field( + &native_field(&runtime, "project")?, + "unmatched_literal_sql_relations", + )? + .is_truthy()?; if !include_project { for item in selected { - let existing = snapshot - .getattr("existing_relations")? + let existing = native_field(&snapshot, "existing_relations")? .call_method1("get", (native_field(item, "name")?,))?; if !existing.is_none() && host::entry(py, AdapterHost::NormalizeRelationType)? @@ -74,8 +74,7 @@ pub(crate) fn plan_old_name_views<'py>( let runtime = phase.runtime.clone(); let scope = phase.scope.clone(); let project = runtime_field(&runtime, |runtime| &runtime.project)?; - let models: Vec> = project - .getattr("models")? + let models: Vec> = native_field(&project, "models")? .try_iter()? .collect::>()?; let by_name = PyDict::new(py); @@ -105,7 +104,7 @@ pub(crate) fn plan_old_name_views<'py>( let mut views: Vec> = Vec::new(); for history in native_field(&state, "old_name_histories")?.try_iter()? { let history = history?; - let history_target = native_field(&history, "move")?.getattr("target_name")?; + let history_target = native_field(&native_field(&history, "move")?, "target_name")?; if !history_target.is_none() && !history_target.eq(&target)? { continue; } 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 b5a19c38fc..73660a9ae4 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 @@ -53,9 +53,10 @@ pub(super) fn errors<'py>( entries: &Bound<'py, PyAny>, ) -> PyResult>> { let py = runtime.py(); - let literals = runtime - .getattr("project")? - .getattr("unmatched_literal_sql_relations")?; + let literals = native_field( + &native_field(runtime, "project")?, + "unmatched_literal_sql_relations", + )?; if !literals.is_truthy()? { return Ok(Vec::new()); } @@ -100,7 +101,7 @@ pub(super) fn errors<'py>( } let mut parts: Vec = Vec::new(); for field in ["database", "schema", "name"] { - let part = relation.getattr(field)?; + let part = native_field(&relation, field)?; if part.is_truthy()? { parts.push(part.extract()?); } @@ -120,8 +121,7 @@ pub(super) fn errors<'py>( &native_field(&matched, "ref")?, None, )?; - let path = literal - .getattr("relative_path")? + let path = native_field(&literal, "relative_path")? .str()? .to_str()? .to_owned(); 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 1b846482ba..b3e1d78400 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 @@ -36,7 +36,7 @@ pub(super) fn redirected_cursor_snapshots<'py>( let start = if config.is_none() { py.None().into_bound(py) } else { - native_field(&config, "cursors")?.getattr("start")? + native_field(&native_field(&config, "cursors")?, "start")? }; let cursor_scope = native( py, 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 8763786f8b..d6ccd21cd0 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 @@ -49,11 +49,11 @@ pub(super) fn migration_with_execution<'py>( let transient = if native_field(&adapter, "adapter_name")?.eq("snowflake")? { PyBool::new( py, - model - .getattr("config")? - .getattr("table_type")? - .getattr("value")? - .eq("transient")?, + native_field( + &native_field(&native_field(model, "config")?, "table_type")?, + "value", + )? + .eq("transient")?, ) .to_owned() .into_any() 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 3139915f87..4b325ae02c 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 @@ -17,9 +17,7 @@ pub(super) fn location<'py>( fields: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { let qualified = call_with( - &runtime - .getattr("adapter")? - .getattr("render_qualified_name")?, + &native_field(&native_field(runtime, "adapter")?, "render_qualified_name")?, fields, )?; let mut fields = fields.to_vec(); @@ -33,9 +31,11 @@ fn named<'py>( runtime: &Bound<'py, PyAny>, state: &Bound<'py, PyAny>, ) -> PyResult<(Bound<'py, PyAny>, Bound<'py, PyAny>)> { - for item in runtime_field(runtime, |runtime| &runtime.project)? - .getattr("models")? - .try_iter()? + for item in native_field( + &runtime_field(runtime, |runtime| &runtime.project)?, + "models", + )? + .try_iter()? { let item = item?; if native_field(&item, "name")?.eq(name)? { @@ -74,7 +74,7 @@ pub(super) fn planning_database<'py>(runtime: &Bound<'py, PyAny>) -> PyResult(runtime: &Bound<'py, PyAny>) -> PyResult(runtime: &Bound<'py, PyAny>) -> PyResult> { let schemas = PySet::empty(runtime.py())?; - for item in runtime_field(runtime, |runtime| &runtime.project)? - .getattr("models")? - .try_iter()? + for item in native_field( + &runtime_field(runtime, |runtime| &runtime.project)?, + "models", + )? + .try_iter()? { - let schema = native_field(&item?, "destination")?.getattr("schema")?; + let schema = native_field(&native_field(&item?, "destination")?, "schema")?; if !schema.is_none() { schemas.add(schema)?; } 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 470131e94e..ebbc7073c1 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 @@ -110,8 +110,7 @@ fn fingerprint_key(fingerprint: &Bound<'_, PyAny>) -> PyResult { }; Ok(FingerprintKey { order: FingerprintOrder { - timestamp: fingerprint - .getattr("ts")? + timestamp: native_field(fingerprint, "ts")? .call_method0("isoformat")? .extract()?, run_id: native_field(fingerprint, "run_id")?.extract()?, 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 fa39d71c99..8adedd54f4 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 @@ -60,7 +60,7 @@ fn config_or<'py>( } let mut value = config.clone(); for name in path { - value = value.getattr(*name)?; + value = native_field(&value, name)?; } Ok(value) } @@ -83,9 +83,10 @@ pub(crate) fn build_planner_entry_results<'py>( deferral: deferral.clone(), project_config: config.clone(), local_config: runtime_field(runtime, |runtime| &runtime.local_config)?, - known_source_columns: warehouse - .getattr("inspection_relations")? - .getattr("source_warehouse_columns")?, + known_source_columns: native_field( + &native_field(warehouse, "inspection_relations")?, + "source_warehouse_columns", + )?, relation_keys: py.None().into_bound(py), })?; let propagation = native_field(source_freshness, "propagation")?; @@ -104,10 +105,10 @@ pub(crate) fn build_planner_entry_results<'py>( let limit_action = if config.is_none() { default_microbatch_limit_action(py)? } else { - config - .getattr("microbatches")? - .getattr("limits")? - .getattr("action")? + native_field( + &native_field(&native_field(&config, "microbatches")?, "limits")?, + "action", + )? }; let build_inputs = Bound::new( py, @@ -115,8 +116,7 @@ pub(crate) fn build_planner_entry_results<'py>( source_freshness_blocked_model_names: blocked.unbind(), external_blocked_model_names: PyFrozenSet::new( py, - overrides - .getattr("external_blocked_model_names")? + native_field(overrides, "external_blocked_model_names")? .try_iter()? .collect::>>()?, )? diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/full_refresh.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/full_refresh.rs index 5f1d7d43c8..7fca1039bc 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/full_refresh.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/full_refresh.rs @@ -11,10 +11,7 @@ use sqlbuild_analysis::planner_warehouse::main::full_rebuild_cause::full_rebuild use sqlbuild_analysis::planner_warehouse::models::FullRebuildFacts; fn configured_full_refresh<'py>(model: &Bound<'py, PyAny>) -> PyResult> { - model - .getattr("config")? - .getattr("values")? - .call_method1("get", ("full_refresh",)) + native_field(&native_field(model, "config")?, "values")?.call_method1("get", ("full_refresh",)) } /// dbt-compatible nullable model full-refresh semantics. @@ -56,8 +53,7 @@ pub(crate) fn describe_full_rebuild_cause( ) -> PyResult { Ok(full_rebuild_cause(&FullRebuildFacts { full_refresh, - change_kind: change_result - .getattr("change_kind")? + change_kind: native_field(change_result, "change_kind")? .str()? .extract::()?, query_changed: native_field(change_result, "query_changed")?.is_truthy()?, 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 be23229cb2..14fb5e8c77 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/identities.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/identities.rs @@ -108,7 +108,7 @@ fn reusable_model_names( execution .getattr(field)? .call_method1("get", (&name,))? - .eq(stale.getattr(field)?.call_method1("get", (&name,))?) + .eq(native_field(&stale, field)?.call_method1("get", (&name,))?) }; if same("model_version_hashes")? && same("model_metadata_jsons")? { names.insert(name.extract()?); @@ -158,15 +158,11 @@ pub(crate) fn detect_stale_warning_changes<'py>( let reusable = reusable_model_names( identities, execution_changes, - project - .getattr("settings")? - .getattr("query_change_tracking")? - .is_truthy()?, + native_field(&native_field(project, "settings")?, "query_change_tracking")?.is_truthy()?, )?; let stale_scope = class_field(scopes, |s: &PlannerScopeResolution| &s.stale_warning_scope)?; let mut remaining: Vec<_> = Vec::new(); - for key in stale_scope - .getattr("all_keys")? + for key in native_field(&stale_scope, "all_keys")? .call_method0("values")? .try_iter()? { @@ -205,8 +201,7 @@ pub(crate) fn detect_stale_warning_changes<'py>( ), )?; let models = PyDict::new(py); - for (name, change) in execution_changes - .getattr("models")? + for (name, change) in native_field(execution_changes, "models")? .downcast::()? .iter() { 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 0a4dea9957..59bc2c2d02 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/objects.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/objects.rs @@ -1,11 +1,12 @@ //! Boundary helpers shared by the native planner phases. -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::config_values::config_str as authored_config_str; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::native_host::models::HostId; use crate::bindings::_helpers::records::construct::{enum_member, new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced_keywords; use crate::bindings::_helpers::records::ids::{ErrorId, NativeItem, RecordId}; use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python}; @@ -54,8 +55,7 @@ pub(crate) fn replace<'py>( value: &Bound<'py, PyAny>, fields: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - imported(value.py(), "dataclasses", "replace")? - .call((value,), Some(&keywords(value.py(), fields)?)) + replaced_keywords(value, &keywords(value.py(), fields)?) } /// A staying-Python enum member from its value. @@ -149,7 +149,7 @@ pub(crate) fn by_name<'py>( field: &str, ) -> PyResult> { let nodes = pyo3::types::PyDict::new(project.py()); - for node in project.getattr(field)?.try_iter()? { + for node in native_field(project, field)?.try_iter()? { let node = node?; pyo3::types::PyDictMethods::set_item(&nodes, native_field(&node, "name")?, node)?; } @@ -158,9 +158,12 @@ pub(crate) fn by_name<'py>( /// A model config value as text, `None` when absent or not text. pub(crate) fn config_str(model: &Bound<'_, PyAny>, key: &str) -> PyResult> { - authored_config_str(&native_field(model, "config")?.getattr("values")?, key)? - .map(|value| value.extract()) - .transpose() + authored_config_str( + &native_field(&native_field(model, "config")?, "values")?, + key, + )? + .map(|value| value.extract()) + .transpose() } /// The compiled object key of the model `name`. 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 3b97c2ce95..422bf9f1eb 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 @@ -171,14 +171,13 @@ pub(crate) fn add_storage_policies<'py>( ) -> PyResult> { let py = runtime.py(); let mut entries = output.field("model_entries")?; - if policies - .getattr("record_migration_fingerprints")? - .is_truthy()? - { + if native_field(policies, "record_migration_fingerprints")?.is_truthy()? { let models = PyDict::new(py); - for model in runtime_field(runtime, |runtime| &runtime.project)? - .getattr("models")? - .try_iter()? + for model in native_field( + &runtime_field(runtime, |runtime| &runtime.project)?, + "models", + )? + .try_iter()? { let model = model?; models.set_item(model_reads::name_object(&model)?, model)?; @@ -200,8 +199,7 @@ pub(crate) fn add_storage_policies<'py>( ), ( "dialect", - runtime - .getattr("adapter")? + native_field(runtime, "adapter")? .call_method0("sql_analysis_dialect")?, ), ], @@ -332,11 +330,10 @@ fn freshness_metadata<'py>(source_freshness: &Bound<'py, PyAny>) -> PyResult = Vec::new(); - for unknown in source_freshness - .getattr("unknown_sources")? + for unknown in native_field(source_freshness, "unknown_sources")? .call_method0("values")? .try_iter()? { @@ -345,7 +342,7 @@ fn freshness_metadata<'py>(source_freshness: &Bound<'py, PyAny>) -> PyResult( let py = runtime.py(); let project_config = runtime_field(runtime, |runtime| &runtime.project_config)?; let local_config = runtime_field(runtime, |runtime| &runtime.local_config)?; - let target_name = runtime - .getattr("project")? - .getattr("effective_target_name")?; + let target_name = native_field(&native_field(runtime, "project")?, "effective_target_name")?; if project_config.is_none() || local_config.is_none() || target_name.is_none() { return native(py, RecordId::TargetConfig, &[]); } @@ -59,9 +57,11 @@ fn selected_models<'py>( .into_iter() .collect(); let mut models: Vec<_> = Vec::new(); - for model in runtime_field(runtime, |runtime| &runtime.project)? - .getattr("models")? - .try_iter()? + for model in native_field( + &runtime_field(runtime, |runtime| &runtime.project)?, + "models", + )? + .try_iter()? { let model = model?; if names.contains(&model_reads::model_name(&model)?) { @@ -72,10 +72,7 @@ fn selected_models<'py>( } fn adapter_is(runtime: &Bound<'_, PyAny>, name: &str) -> PyResult { - runtime - .getattr("adapter")? - .getattr("adapter_name")? - .eq(name) + native_field(&native_field(runtime, "adapter")?, "adapter_name")?.eq(name) } /// Entries for selected Snowflake table-type drift. @@ -91,11 +88,11 @@ pub(crate) fn plan_table_types<'py>( let snowflake = adapter_is(runtime, SNOWFLAKE_ADAPTER)?; let mut declared: Vec<_> = Vec::new(); for model in &models { - if model - .getattr("config")? - .getattr("table_type")? - .getattr("declared")? - .is_truthy()? + if native_field( + &native_field(&native_field(model, "config")?, "table_type")?, + "declared", + )? + .is_truthy()? { declared.push(model_reads::model_name(model)?); } @@ -114,9 +111,7 @@ pub(crate) fn plan_table_types<'py>( return tuple(py, Vec::new()); } let target = effective_target_config(runtime)?; - let relations = warehouse - .getattr("snapshot")? - .getattr("existing_relations")?; + let relations = native_field(&native_field(warehouse, "snapshot")?, "existing_relations")?; let adapter = runtime_field(runtime, |runtime| &runtime.adapter)?; let materialization = record_class(py, RecordId::MaterializationType)?; let mut entries: Vec<_> = Vec::new(); @@ -162,7 +157,7 @@ pub(crate) fn plan_table_types<'py>( ("model_name", model_reads::name_object(&model)?), ("destination", destination), ("copy_name", text(py, ©_name(&target_name, limit))), - ("desired_type", native_field(&table_type, "value")?.getattr("value")?), + ("desired_type", native_field(&native_field(&table_type, "value")?, "value")?), ( "actual_type", optional_text( @@ -176,11 +171,11 @@ pub(crate) fn plan_table_types<'py>( }), ), ), - ("source", native_field(&table_type, "source")?.getattr("value")?), + ("source", native_field(&native_field(&table_type, "source")?, "value")?), ("downgrade", downgrade.into_pyobject(py)?.to_owned().into_any()), ( "downgrade_policy", - native_field(&target, "table_type_downgrade")?.getattr("value")?, + native_field(&native_field(&target, "table_type_downgrade")?, "value")?, ), ( "irreversible_warning", @@ -215,9 +210,10 @@ fn entry<'py>( ) -> PyResult> { let py = base.request.py(); let days = |name: &str| -> PyResult> { - base.state - .as_ref() - .map_or_else(|| Ok(py.None().into_bound(py)), |state| state.getattr(name)) + base.state.as_ref().map_or_else( + || Ok(py.None().into_bound(py)), + |state| native_field(state, name), + ) }; planner_model( py, @@ -268,9 +264,10 @@ fn render_changes<'py>( fields.push(("state", state.clone())); } call_with( - &runtime - .getattr("adapter")? - .getattr("render_retention_changes")?, + &native_field( + &native_field(runtime, "adapter")?, + "render_retention_changes", + )?, &fields, ) } @@ -295,7 +292,7 @@ fn inspect<'py>( } fn state_direction(state: &Bound<'_, PyAny>, desired_days: i64) -> PyResult<&'static str> { - let optional = |name: &str| -> PyResult> { state.getattr(name)?.extract() }; + let optional = |name: &str| -> PyResult> { native_field(state, name)?.extract() }; Ok(retention_direction( [ optional("delta_log_retention_days")?, @@ -363,13 +360,11 @@ fn relation_retention<'py>( request: request.clone(), model_names: tuple(py, vec![model_reads::name_object(model)?])?, state: None, - source: native_field(&retention, "source")?.getattr("value")?, + source: native_field(&native_field(&retention, "source")?, "value")?, }; let snowflake = adapter_is(runtime, SNOWFLAKE_ADAPTER)?; - let table_type = native_field(&config, "table_type")?.getattr("value")?; - let relations = warehouse - .getattr("snapshot")? - .getattr("existing_relations")?; + let table_type = native_field(&native_field(&config, "table_type")?, "value")?; + let relations = native_field(&native_field(warehouse, "snapshot")?, "existing_relations")?; if !relations.contains(model_reads::name_object(model)?)? { if snowflake && desired_days > 1 && table_type.eq(TRANSIENT_TABLE_TYPE)? { return Err(transient_error(py, &name, desired_days, "set ")); @@ -394,9 +389,10 @@ fn relation_retention<'py>( let mut listed = py.None().into_bound(py); if native_field(&relation, "identity")?.eq(&lookup)? { listed = call_with( - &runtime - .getattr("adapter")? - .getattr("retention_state_from_relation")?, + &native_field( + &native_field(runtime, "adapter")?, + "retention_state_from_relation", + )?, &[("request", request.clone()), ("relation", relation.clone())], )?; } @@ -457,10 +453,7 @@ fn bigquery_retention<'py>( ) -> PyResult>> { let py = runtime.py(); let target = effective_target_config(runtime)?; - if !target - .getattr("owns_time_travel_retention_namespace")? - .is_truthy()? - { + if !native_field(&target, "owns_time_travel_retention_namespace")?.is_truthy()? { return Err(planner_error( py, "BigQuery managed time_travel_retention requires target owns_time_travel_retention_namespace = true", @@ -469,12 +462,14 @@ fn bigquery_retention<'py>( } type Scope = (Option, String); let mut managed: BTreeMap>> = BTreeMap::new(); - for model in runtime_field(runtime, |runtime| &runtime.project)? - .getattr("models")? - .try_iter()? + for model in native_field( + &runtime_field(runtime, |runtime| &runtime.project)?, + "models", + )? + .try_iter()? { let model = model?; - let retention = native_field(&model, "config")?.getattr("time_travel_retention")?; + let retention = native_field(&native_field(&model, "config")?, "time_travel_retention")?; let destination = model_reads::destination(&model)?; if native_field(&retention, "unmanaged")?.is_truthy()? || native_field(&retention, "desired_days")?.is_none() @@ -508,7 +503,7 @@ fn bigquery_retention<'py>( let mut sources: BTreeSet = BTreeSet::new(); let mut names: Vec<_> = Vec::new(); for item in &scope_models { - let retention = native_field(item, "config")?.getattr("time_travel_retention")?; + let retention = native_field(&native_field(item, "config")?, "time_travel_retention")?; let desired = native_field(&retention, "desired_days")?; details.push(format!( "{}={}d", @@ -597,7 +592,7 @@ pub(crate) fn plan_retention<'py>( let py = runtime.py(); let mut models: Vec<_> = Vec::new(); for model in selected_models(runtime, scope)? { - let retention = native_field(&model, "config")?.getattr("time_travel_retention")?; + let retention = native_field(&native_field(&model, "config")?, "time_travel_retention")?; if !native_field(&retention, "unmanaged")?.is_truthy()? && !native_field(&retention, "desired_days")?.is_none() { @@ -616,9 +611,13 @@ pub(crate) fn plan_retention<'py>( } entries }; - let policy = effective_target_config(runtime)? - .getattr("time_travel_retention_decrease")? - .getattr("value")?; + let policy = native_field( + &native_field( + &effective_target_config(runtime)?, + "time_travel_retention_decrease", + )?, + "value", + )?; let mut decided: Vec<_> = Vec::new(); for entry in entries { decided.push(replace(&entry, &[("decrease_policy", policy.clone())])?); 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 9784b71f37..ae15c80b06 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 @@ -73,7 +73,7 @@ pub(crate) fn gather_planner_warehouse_state<'py>( if config.is_none() { py.None().into_bound(py) } else { - native_field(&config, "cursors")?.getattr("start")? + native_field(&native_field(&config, "cursors")?, "start")? }, ), ( @@ -85,11 +85,13 @@ pub(crate) fn gather_planner_warehouse_state<'py>( let adapter = runtime_field(runtime, |runtime| &runtime.adapter)?; let stale_keys = pyo3::types::PyFrozenSet::new( py, - class_field(scopes, |s: &PlannerScopeResolution| &s.stale_warning_scope)? - .getattr("all_keys")? - .call_method0("values")? - .try_iter()? - .collect::>>()?, + native_field( + &class_field(scopes, |s: &PlannerScopeResolution| &s.stale_warning_scope)?, + "all_keys", + )? + .call_method0("values")? + .try_iter()? + .collect::>>()?, )? .into_any(); let snapshot = warehouse_snapshot(&SnapshotRequest { @@ -126,11 +128,7 @@ pub(crate) fn gather_planner_warehouse_state<'py>( })?; let physical = PyDict::new(py); for entry in &migrations.entries { - if entry - .getattr("decision")? - .getattr("moves_data")? - .is_truthy()? - { + if native_field(&native_field(entry, "decision")?, "moves_data")?.is_truthy()? { let origin = native_field(entry, "origin")?; let qualified = native_field(&origin, "qualified_name")?; physical.set_item( 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 f407a983f5..b340482346 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 @@ -80,7 +80,7 @@ pub(super) fn duration(value: &Bound<'_, PyAny>) -> PyResult PyResult { parsed.getattr(name)?.extract() }; + let field = |name: &str| -> PyResult { native_field(&parsed, name)?.extract() }; Ok(Some(DurationSpan { months: field("years")? * 12 + field("months")?, days: field("days")?, 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 4b78c970fb..98c8ccb4f9 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 @@ -35,7 +35,7 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::{IntoPyObject, PyErr}; fn config<'py>(model: &Bound<'py, PyAny>, key: &str) -> PyResult> { - let values: Bound<'py, PyAny> = native_field(model, "config")?.getattr("values")?; + let values: Bound<'py, PyAny> = native_field(&native_field(model, "config")?, "values")?; let value: Option> = if key == CURSOR_START { config_cursor_bound(&values, key)? } else { @@ -57,8 +57,7 @@ fn unbounded(py: Python<'_>) -> PyResult> { } fn destination_exists(model: &Bound<'_, PyAny>, snapshot: &Bound<'_, PyAny>) -> PyResult { - let relation = snapshot - .getattr("existing_relations")? + let relation = native_field(snapshot, "existing_relations")? .call_method1("get", (model_reads::name_object(model)?,))?; if relation.is_none() { return Ok(false); @@ -122,8 +121,7 @@ pub(crate) fn compute_model_cursor_bounds<'py>( return Ok(bounded); } let snapshot = &request.snapshot; - let mut cursor_snapshot = snapshot - .getattr("cursor_snapshots")? + let mut cursor_snapshot = native_field(snapshot, "cursor_snapshots")? .call_method1("get", (model_reads::name_object(&model)?,))?; let roles = resolve_cursor_input_roles(&model)?; let producers = class_field(context, |c: &ModelPlanContext| { @@ -168,10 +166,7 @@ pub(crate) fn compute_model_cursor_bounds<'py>( if empty.is_truthy()? { return empty_cursor_window(&cursor_type, &config(&model, "cursor_start")?); } - if !cursor_snapshot - .getattr("watermarks_available")? - .is_truthy()? - { + if !native_field(&cursor_snapshot, "watermarks_available")?.is_truthy()? { let unreadable = native_field(&cursor_snapshot, "unreadable_input_names")?; let names = if unreadable.is_truthy()? { unreadable 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 09726eecf2..fc58ce0f19 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 @@ -48,7 +48,7 @@ pub(crate) fn type_cursor_overrides<'py>( } let mut models: Vec<(String, String)> = Vec::new(); for model in selected { - let values = native_field(model, "config")?.getattr("values")?; + let values = native_field(&native_field(model, "config")?, "values")?; let config = |key: &str| -> PyResult> { Ok(authored_config_str(&values, key)?.unwrap_or_else(|| py.None().into_bound(py))) }; @@ -63,7 +63,7 @@ pub(crate) fn type_cursor_overrides<'py>( let mut integer_literals = true; if models.is_empty() { for field in ["start", "end"] { - let value = overrides.getattr(field)?; + let value = native_field(overrides, field)?; if value.is_none() { continue; } 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 64622d1b47..d0a1c85447 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 @@ -44,7 +44,7 @@ fn resolve_policy<'py>( } else { class.call0()? }; - let values = native_field(model, "config")?.getattr("values")?; + let values = native_field(&native_field(model, "config")?, "values")?; let raw_distance = values.call_method1("get", (fields.distance_key,))?; let raw_action = values.call_method1("get", (fields.action_key,))?; let mut distance = configured.getattr(fields.distance)?; 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 96d41b9216..ec9d3d7352 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 @@ -82,10 +82,11 @@ pub(crate) struct CursorWindowInputs<'py> { /// One string-valued model configuration entry. pub(crate) fn config_str<'py>(model: &Bound<'py, PyAny>, key: &str) -> PyResult> { - Ok( - authored_config_str(&native_field(model, "config")?.getattr("values")?, key)? - .unwrap_or_else(|| model.py().None().into_bound(model.py())), - ) + Ok(authored_config_str( + &native_field(&native_field(model, "config")?, "values")?, + key, + )? + .unwrap_or_else(|| model.py().None().into_bound(model.py()))) } /// One cursor-bound model configuration entry as its token text. @@ -95,7 +96,10 @@ pub(crate) fn config_bound<'py>( ) -> PyResult> { object( model.py(), - config_cursor_bound(&native_field(model, "config")?.getattr("values")?, key)?, + config_cursor_bound( + &native_field(&native_field(model, "config")?, "values")?, + key, + )?, ) } @@ -360,8 +364,7 @@ fn model_cursor_snapshot<'py>( model: &Bound<'py, PyAny>, snapshot: &Bound<'py, PyAny>, ) -> PyResult> { - snapshot - .getattr("cursor_snapshots")? + native_field(snapshot, "cursor_snapshots")? .call_method1("get", (model_reads::name_object(model)?,)) } @@ -548,11 +551,11 @@ pub(crate) fn model_cursor_overrides<'py>( None }; if let Some((start_field, end_field)) = typed_fields { - let typed_start = cursor_overrides.getattr(start_field)?; + let typed_start = native_field(cursor_overrides, start_field)?; if !typed_start.is_none() { resolved_start = token(py, &typed_start)?; } - let typed_end = cursor_overrides.getattr(end_field)?; + let typed_end = native_field(cursor_overrides, end_field)?; if !typed_end.is_none() { resolved_end = token(py, &typed_end)?; } 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 58384629be..ce18e918e7 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 @@ -5,6 +5,7 @@ use crate::bindings::_helpers::cursor::conversion::CursorSession; use crate::bindings::_helpers::planner_resolution::cursor_bounds::duration; use crate::bindings::_helpers::records::construct::{new_record_with, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced_keywords; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python}; @@ -63,11 +64,7 @@ pub(super) fn horizon<'py>( let (clock, grain) = clock_and_grain; let mut clock = utc_clock(clock)?; let date_only = discovered.is_instance(&record_class(py, RecordId::DateValue)?)?; - if date_only - || native_field(discovered, "value")? - .getattr("tzinfo")? - .is_none() - { + if date_only || native_field(&native_field(discovered, "value")?, "tzinfo")?.is_none() { clock = call_with( &native_field(&clock, "replace")?, &[("tzinfo", py.None().into_bound(py))], @@ -91,6 +88,5 @@ pub(super) fn replace<'py>( object: &Bound<'py, PyAny>, fields: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - imported(object.py(), "dataclasses", "replace")? - .call((object,), Some(&keywords(object.py(), fields)?)) + replaced_keywords(object, &keywords(object.py(), fields)?) } 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 de11c1d389..a3e08a42b8 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 @@ -13,7 +13,7 @@ const FUTURE_CAPPED_CODE: &str = "FUTURE_CURSOR_CAPPED"; const START_CAPPED_CODE: &str = "AUTOMATIC_START_CAPPED"; fn rendered(evidence: &Bound<'_, PyAny>, field: &str) -> PyResult { - render_value(&evidence.getattr(field)?) + render_value(&native_field(evidence, field)?) } /// Render a prominent warning for applied future cursor policy. @@ -58,7 +58,7 @@ pub(crate) fn maximum_start_message(bounds: &Bound<'_, PyAny>) -> PyResult( pub(crate) fn cursor_inputs_without_rows<'py>( cursor_snapshot: &Bound<'py, PyAny>, ) -> PyResult> { - if cursor_snapshot - .getattr("unreadable_input_names")? - .is_truthy()? - { + if native_field(cursor_snapshot, "unreadable_input_names")?.is_truthy()? { return new_record( cursor_snapshot.py(), RecordId::EmptyCursorInputDecision, @@ -58,22 +56,14 @@ pub(crate) fn cursor_inputs_without_rows<'py>( ); } let rows = native_field(cursor_snapshot, "upstream_maxes")?.is_truthy()? - || cursor_snapshot - .getattr("upstream_terminal_ends")? - .is_truthy()? - || cursor_snapshot - .getattr("upstream_availability_ends")? - .is_truthy()?; + || native_field(cursor_snapshot, "upstream_terminal_ends")?.is_truthy()? + || native_field(cursor_snapshot, "upstream_availability_ends")?.is_truthy()?; let start = !native_field(cursor_snapshot, "target_max")?.is_none() || native_field(cursor_snapshot, "upstream_mins")?.is_truthy()? - || cursor_snapshot - .getattr("upstream_terminal_starts")? - .is_truthy()?; + || native_field(cursor_snapshot, "upstream_terminal_starts")?.is_truthy()?; decide_empty_cursor_inputs( &native_field(cursor_snapshot, "empty_input_names")?, - !cursor_snapshot - .getattr("cursor_watermark_mode")? - .eq("any")?, + !native_field(cursor_snapshot, "cursor_watermark_mode")?.eq("any")?, rows, start, ) @@ -189,7 +179,7 @@ pub(crate) fn without_destination_cursor<'py>( for name in ["target_max", "physical_target_max", "target_eligible_max"] { kwargs.set_item(name, py.None())?; } - imported(py, "dataclasses", "replace")?.call((cursor_snapshot,), Some(&kwargs)) + replaced_keywords(cursor_snapshot, &kwargs) } #[pyfunction] @@ -248,7 +238,7 @@ pub(crate) fn normalize_cursor_snapshot_grain<'py>( }; let kwargs = PyDict::new(py); for key in ["target_max", "target_eligible_max"] { - kwargs.set_item(key, normalize(cursor_snapshot.getattr(key)?)?)?; + kwargs.set_item(key, normalize(native_field(cursor_snapshot, key)?)?)?; } for key in ["upstream_mins", "upstream_maxes"] { let values = cursor_snapshot @@ -273,7 +263,7 @@ pub(crate) fn normalize_cursor_snapshot_grain<'py>( ends.push((normalize(pair.get_item(0)?)?, pair.get_item(1)?)); } kwargs.set_item("upstream_end_inputs", PyTuple::new(py, ends)?)?; - imported(py, "dataclasses", "replace")?.call((cursor_snapshot,), Some(&kwargs)) + replaced_keywords(cursor_snapshot, &kwargs) } pub(super) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { 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 884f3466ae..56c1be8f6d 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 @@ -115,7 +115,7 @@ pub(crate) fn resolve_future_cursor_safety<'py>( }; let determining_field = |field: &str| -> PyResult> { if let Some((item, _)) = &determining { - item.getattr(field) + native_field(item, field) } else { Ok(py.None().into_bound(py)) } 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 9c1863ff17..776e136cfb 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 @@ -276,7 +276,7 @@ fn exceeded_message(evidence: &Bound<'_, PyAny>, non_idempotent: bool) -> PyResu rendered(&native_field(evidence, "physical_target_max")?)?, rendered(&native_field(evidence, "effective_start")?)?, rendered(&native_field(evidence, "maximum_allowed_start")?)?, - native_field(evidence, "action")?.getattr("value")?.str()?, + native_field(&native_field(evidence, "action")?, "value")?.str()?, native_field(evidence, "target_relation")?.str()?, native_field(evidence, "cursor_column")?.str()?, )) 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 7afaa0d38e..41b40b77f5 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/orchestration.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/orchestration.rs @@ -4,9 +4,7 @@ use crate::bindings::_helpers::analysis::model_reads; use crate::bindings::_helpers::attachments::cursor_intrinsics::resolve_cursor_intrinsics; use crate::bindings::_helpers::attachments::reference_markers::assert_no_unresolved_sql_markers; use crate::bindings::_helpers::compile_attachment::cursor_roles::resolve_cursor_input_roles; -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::scalars::sentinel_to_token; use crate::bindings::_helpers::model_config::config_values::config_str as authored_config_str; use crate::bindings::_helpers::planner_entries::models::ModelPlanContext; @@ -20,6 +18,7 @@ use crate::bindings::_helpers::planner_resolution::refs::{ use crate::bindings::_helpers::planner_resolution::sources::resolve_sources; use crate::bindings::_helpers::records::construct::record_class; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced_keywords; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; @@ -113,7 +112,7 @@ impl<'py> Resolution<'py> { "statement", self.unbounded(native_field(&value, "statement")?, label)?, )?; - return imported(py, "dataclasses", "replace")?.call((value,), Some(&kwargs)); + return replaced_keywords(&value, &kwargs); } let is_list = value.is_instance_of::(); if is_list || value.is_instance_of::() { @@ -195,7 +194,7 @@ pub(crate) fn resolve_model_sql<'py>( let model = &request.model; let py = model.py(); let cursor_type = authored_config_str( - &native_field(model, "config")?.getattr("values")?, + &native_field(&native_field(model, "config")?, "values")?, "cursor_type", )? .unwrap_or_else(|| py.None().into_bound(py)); @@ -232,13 +231,11 @@ pub(crate) fn resolve_model_sql<'py>( fn append_cursor_inclusive(model: &Bound<'_, PyAny>) -> PyResult { let py = model.py(); let strategy = authored_config_str( - &native_field(model, "config")?.getattr("values")?, + &native_field(&native_field(model, "config")?, "values")?, "incremental_strategy", )? .unwrap_or_else(|| py.None().into_bound(py)); - let raw = model - .getattr("config")? - .getattr("values")? + let raw = native_field(&native_field(model, "config")?, "values")? .call_method1("get", ("append_cursor_inclusive",))?; Ok(!strategy.eq("append")? || !raw.is_instance_of::() || raw.is_truthy()?) } @@ -257,7 +254,8 @@ fn intrinsic_bounds<'py>( &[ ( "value", - PyString::new(py, &sentinel_to_token(&bounds.getattr(field)?)?).into_any(), + PyString::new(py, &sentinel_to_token(&native_field(bounds, field)?)?) + .into_any(), ), ("cursor_type", cursor_type.clone()), ], @@ -342,7 +340,7 @@ pub(crate) fn model_hook_entries<'py>( model: &Bound<'py, PyAny>, resolution: &Resolution<'py>, ) -> PyResult> { - let values = native_field(model, "config")?.getattr("values")?; + let values = native_field(&native_field(model, "config")?, "values")?; let name: String = model_reads::model_name(model)?; let mut hooks: Vec> = Vec::new(); for key in ["pre_hooks", "post_hooks"] { 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 4d3d5f2379..75d986c8cd 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/sources.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/sources.rs @@ -65,8 +65,7 @@ fn check_columns( "S401", )); } - if entry - .getattr("contract")? + if native_field(entry, "contract")? .extract::>()? .as_deref() != Some("enforced") @@ -265,8 +264,7 @@ fn resolve_one( let expression = !native_field(&entry, "expression")?.is_none(); if columns.is_truthy()? && (!expression - || entry - .getattr("contract")? + || native_field(&entry, "contract")? .extract::>()? .as_deref() == Some("enforced")) 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 0bcd5c6a96..adba771ab2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_resolution/values.rs @@ -3,6 +3,7 @@ use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::cursor::scalars::sentinel_to_token; use crate::bindings::_helpers::planner_resolution::models::CursorFiltering; +use crate::bindings::_helpers::records::fields::field as native_field; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::PyString; use sqlbuild_sqltext::resolution::main::cursor_subquery::cursor_subquery; @@ -17,7 +18,7 @@ pub(super) fn method<'py>( name: &str, values: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - call_with(&object.getattr(name)?, values) + call_with(&native_field(object, name)?, values) } pub(super) fn cursor_wrap( @@ -40,7 +41,7 @@ pub(super) fn cursor_wrap( let py = adapter.py(); let cursor_type = &filtering.cursor_type; let literal = |bound: &str| -> PyResult { - let token = sentinel_to_token(&bounds.getattr(bound)?)?; + let token = sentinel_to_token(&native_field(bounds, bound)?)?; method( adapter, "render_cursor_bound_literal", 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 a3ba61f1fe..dc13c15357 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/artifacts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/artifacts.rs @@ -152,9 +152,11 @@ pub(crate) fn build_scenario_artifact_name( identifier_limit: i64, ) -> PyResult { let py = kind.py(); - let kind: String = enum_from_value(py, RecordId::ScenarioArtifactKind, kind)? - .getattr("value")? - .extract()?; + let kind: String = native_field( + &enum_from_value(py, RecordId::ScenarioArtifactKind, kind)?, + "value", + )? + .extract()?; build_scenario_artifact_physical_name(hash_prefix, &kind, logical_name, identifier_limit) } 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 d84016d221..f8d04366c5 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/cli.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/cli.rs @@ -41,7 +41,7 @@ pub(crate) fn build_cli_scenario_plan<'py>( ), )?; let scenario = args.value("scenario")?; - let project = args.value("pipeline_result")?.getattr("project")?; + let project = native_field(&args.value("pipeline_result")?, "project")?; let adapter = args.value("adapter")?; let (graph, warnings) = plan_scenario_graph(&scenario, &project, true, None)?; raise_for_warnings(warnings.as_any())?; @@ -56,7 +56,7 @@ pub(crate) fn build_cli_scenario_plan<'py>( ("scenarios", native_field(&project, "sql_scenarios")?), ( "run_namespace", - native_field(&project, "scenario")?.getattr("run_namespace")?, + native_field(&native_field(&project, "scenario")?, "run_namespace")?, ), ], )?), @@ -70,7 +70,7 @@ pub(crate) fn build_cli_scenario_plan<'py>( ("seed_names", "SEED"), ("model_names", "MODEL"), ] { - for name in graph.getattr(field)?.try_iter()? { + for name in native_field(&graph, field)?.try_iter()? { identities.push(new_record_with( py, RecordId::ScenarioArtifactIdentity, @@ -143,7 +143,7 @@ fn namespace<'py>( models.set_item(model_reads::name_object(&model)?, model)?; } for field in ["target_model_names", "model_names"] { - for name in graph.getattr(field)?.try_iter()? { + for name in native_field(graph, field)?.try_iter()? { if let Some(model) = models.get_item(name?)? { let destination = model_reads::destination(&model)?; return Ok(( 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 6e51ce0400..5febab502a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/entries.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/entries.rs @@ -56,12 +56,12 @@ pub(super) fn seed_entries<'py>( ("destination", destination), ( "file_path", - native_field(&seed, "seed_file")?.getattr("file_path")?, + native_field(&native_field(&seed, "seed_file")?, "file_path")?, ), ("columns", extract_seed_columns(&seed)?.into_any()), ( "csv_settings", - native_field(&seed, "schema_entry")?.getattr("csv_settings")?, + native_field(&native_field(&seed, "schema_entry")?, "csv_settings")?, ), ], )?); @@ -113,7 +113,7 @@ pub(super) fn function_entry<'py>( "arguments", "returns", ] { - fields.push((name, function.getattr(name)?)); + fields.push((name, native_field(function, name)?)); } fields.push(("body_sql", body)); fields.push(( @@ -129,7 +129,7 @@ pub(super) fn function_entry<'py>( "entry_point", "packages", ] { - fields.push((name, function.getattr(name)?)); + fields.push((name, native_field(function, name)?)); } new_record_with(py, RecordId::FunctionPlanEntry, &fields) } 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 4c04c67a14..333241eb0f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/execution.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/execution.rs @@ -1,8 +1,8 @@ //! Assemble scenario execution plans through existing planner and adapter APIs. use crate::bindings::_helpers::analysis::model_reads; +use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; use crate::bindings::_helpers::compile_driver::python_calls::keywords; -use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported}; use crate::bindings::_helpers::compiled_project::loop_classes::by_attribute; use crate::bindings::_helpers::compiled_project::records::compiled_model_record; use crate::bindings::_helpers::planner_entries::model_entries::plan_model; @@ -23,6 +23,7 @@ use crate::bindings::_helpers::planner_strategy::fixture_completion::complete_fi use crate::bindings::_helpers::planner_strategy::fixture_context::planning_context; use crate::bindings::_helpers::records::construct::{enum_member, new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced_keywords; use crate::bindings::_helpers::records::ids::RecordId; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python, pyfunction}; use pyo3::types::{PyBool, PyDict, PyDictMethods, PyFrozenSet, PyTuple}; @@ -89,9 +90,7 @@ pub(crate) fn build_scenario_execution_plan<'py>( for key in ordered.try_iter()? { let key = key?; if native_field(&key, "resource_type")?.eq("model")? - && graph - .getattr("model_names")? - .contains(native_field(&key, "name")?)? + && native_field(&graph, "model_names")?.contains(native_field(&key, "name")?)? { model_keys.push(key); } @@ -132,7 +131,7 @@ pub(crate) fn build_scenario_execution_plan<'py>( 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 = replaced_keywords(&record, &changes)?; let model_context = model_context(&project, &context, (&models, &locations, &columns))?; let result = object( py, 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 38eb8b00db..e9c6175598 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/fixtures.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/fixtures.rs @@ -70,7 +70,7 @@ pub(super) fn fixture_plans<'py>( ), ] { let fixtures = extract_fixtures(scenario, &format!("__{kind}__"))?; - for name in graph.getattr(names)?.try_iter()? { + for name in native_field(graph, names)?.try_iter()? { let name: String = name?.extract()?; let target = required_target(&context.relations, locations, &name, kind)?; let Some(mut sql) = fixtures.get_item(&name)? else { 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 f3be3d84ae..7adf670153 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/graph.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/graph.rs @@ -25,9 +25,7 @@ pub(crate) fn plan_scenario_graph<'py>( let mut input = ScenarioGraphInput { name: native_field(scenario, "name")?.extract()?, expected: native_field(scenario, "expected_model_names")?.extract()?, - assertions: scenario - .getattr("assertion_target_model_names")? - .extract()?, + assertions: native_field(scenario, "assertion_target_model_names")?.extract()?, targets: native_field(scenario, "target_model_names")?.extract()?, refs: native_field(scenario, "ref_fixture_names")?.extract()?, sources: native_field(scenario, "source_fixture_names")?.extract()?, @@ -37,8 +35,7 @@ pub(crate) fn plan_scenario_graph<'py>( }; for model in native_field(project, "models")?.try_iter()? { let model = model?; - let deps = model - .getattr("deps")? + let deps = native_field(&model, "deps")? .try_iter()? .map(|dep| native_key(&dep?)) .collect::>()?; 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 e18fe48ad9..2e75f9a3f8 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/relations.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/relations.rs @@ -1,8 +1,6 @@ //! Build scenario relation locations through adapter qualification 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::{REF, SOURCE}; use crate::bindings::_helpers::planner_scenario::constants::{ SCENARIO_PLAN_INTERNAL, SCENARIO_PLAN_MISSING_RELATION_TARGET, @@ -11,6 +9,7 @@ use crate::bindings::_helpers::planner_scenario::models::ScenarioRelations; use crate::bindings::_helpers::planner_scenario::values::error; use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced_keywords; use crate::bindings::_helpers::records::ids::RecordId; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python, pyfunction}; use pyo3::types::{PyDict, PyDictMethods}; @@ -85,7 +84,7 @@ pub(crate) fn build_scenario_relation_plan<'py>( ("dbt_ref_fixture_names", "dbt_ref", &dbt_refs), ("seed_names", "seed", &seeds), ] { - for name in graph.getattr(field)?.try_iter()? { + for name in native_field(graph, field)?.try_iter()? { let name: String = name?.extract()?; let target = target_for_artifact(args, &artifacts, kind, &name)?; locations.set_item(&name, &target)?; @@ -97,13 +96,12 @@ pub(crate) fn build_scenario_relation_plan<'py>( .ok_or_else(|| pyo3::exceptions::PyKeyError::new_err(name.clone()))?; let changes = PyDict::new(py); for field in ["database", "schema"] { - changes.set_item(field, target.getattr(field)?)?; + changes.set_item(field, native_field(&target, field)?)?; } changes.set_item("table", native_field(&target, "name")?)?; changes.set_item("expression", py.None())?; changes.set_item("type_enforcement", false)?; - let replacement = - imported(py, "dataclasses", "replace")?.call((entry,), Some(&changes))?; + let replacement = replaced_keywords(&entry, &changes)?; source_map.set_item(&name, replacement)?; } } 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 14e10ff9f6..ee1b1115ea 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/sql.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scenario/sql.rs @@ -105,7 +105,7 @@ pub(super) fn required_target<'py>( name: &str, kind: &str, ) -> PyResult> { - let target = relations.getattr(field)?.call_method1("get", (name,))?; + let target = native_field(relations, field)?.call_method1("get", (name,))?; if target.is_none() { return Err(error( relations.py(), 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 d803377b22..2fb987e094 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/loaders.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/loaders.rs @@ -54,7 +54,7 @@ pub(crate) fn loader_to_source_entry<'py>( kwargs.set_item("name", &name)?; kwargs.set_item("loader", &name)?; for field in ["database", "schema", "table"] { - kwargs.set_item(field, parts.getattr(field)?)?; + kwargs.set_item(field, native_field(&parts, field)?)?; } for field in [ "write_strategy", @@ -63,7 +63,7 @@ pub(crate) fn loader_to_source_entry<'py>( "contract", "columns", ] { - kwargs.set_item(field, loader.getattr(field)?)?; + kwargs.set_item(field, native_field(loader, field)?)?; } let meta = PyDict::new(py); meta.set_item("sqlbuild_loader_node", true)?; 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 c5452eb291..e83da0b555 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/scope.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/scope.rs @@ -34,10 +34,7 @@ pub(crate) fn managed_source_upstream_keys<'py>( let mut managed: HashSet = HashSet::new(); for source in native_field(project, "sources")?.try_iter()? { let source = source?; - if !native_field(&source, "source_entry")? - .getattr("loader")? - .is_none() - { + if !native_field(&native_field(&source, "source_entry")?, "loader")?.is_none() { managed.insert(native_field(&source, "name")?.extract()?); } } 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 9a555521d0..5aca0fa350 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/sources.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_scope/sources.rs @@ -83,7 +83,7 @@ pub(crate) fn build_source_load_entries<'py>( "cursor_column", "unique_key", ] { - kwargs.set_item(field, entry.getattr(field)?)?; + kwargs.set_item(field, native_field(&entry, field)?)?; } let table = native_field(&entry, "table")?; kwargs.set_item( @@ -94,8 +94,7 @@ pub(crate) fn build_source_load_entries<'py>( native_field(&entry, "name")? }, )?; - let intermediate = entry - .getattr("meta")? + let intermediate = native_field(&entry, "meta")? .call_method1("get", ("sqlbuild_loader_node",))? .is(PyBool::new(py, true)); kwargs.set_item( 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 02a8754ce5..ab4eb62687 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 @@ -58,10 +58,8 @@ fn split_selector_expansion<'py>(raw: &Bound<'py, PyString>) -> PyResult) -> PyResult> { - let tags: Bound<'_, PyAny> = model - .getattr("config")? - .getattr("values")? - .call_method1("get", ("tags",))?; + let tags: Bound<'_, PyAny> = + native_field(&native_field(model, "config")?, "values")?.call_method1("get", ("tags",))?; if !tags.is_instance_of::() && !tags.is_instance_of::() { return Ok(Vec::new()); } @@ -72,9 +70,7 @@ fn selectable_model(model: &Bound<'_, PyAny>) -> PyResult { Ok(SelectableModel { name: native_field(model, "name")?.extract()?, tags: model_tags(model)?, - folder: model - .getattr("relative_path")? - .getattr("parent")? + folder: native_field(&native_field(model, "relative_path")?, "parent")? .call_method0("as_posix")? .extract()?, }) @@ -88,8 +84,7 @@ fn resolve_sqlbuild_model_selector_names<'py>( term: &Bound<'py, PyString>, ) -> PyResult> { let py: Python<'py> = project.py(); - let models: Vec = project - .getattr("models")? + let models: Vec = native_field(project, "models")? .try_iter()? .map(|model| selectable_model(&model?)) .collect::>()?; 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 6ac509d8a7..388b23df60 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 @@ -56,19 +56,19 @@ fn unit_test_names(discovered: &Bound<'_, PyAny>) -> PyResult> fn resource_names(discovered: &Bound<'_, PyAny>) -> PyResult> { let mut names: BTreeSet = BTreeSet::new(); for field in RESOURCE_PATH_FILES { - for file in discovered.getattr(field)?.try_iter()? { + for file in native_field(discovered, field)?.try_iter()? { names.insert(stem(&native_field(&file?, "file_path")?)?); } } for (field, entries) in RESOURCE_ENTRY_FILES { - for file in discovered.getattr(field)?.try_iter()? { + for file in native_field(discovered, field)?.try_iter()? { for entry in file?.getattr(entries)?.try_iter()? { names.insert(native_field(&entry?, "name")?.extract()?); } } } for field in RESOURCE_NODES { - for node in discovered.getattr(field)?.try_iter()? { + for node in native_field(discovered, field)?.try_iter()? { names.insert(native_field(&node?, "name")?.extract()?); } } 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 f6e08bae68..33b174bb4b 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 @@ -60,7 +60,7 @@ fn audit_enum<'py>( default: &str, ) -> PyResult> { let py = audit.py(); - let value = audit.getattr(attribute)?; + let value = native_field(audit, attribute)?; imported(py, AUDIT_TYPES_MODULE, enum_name)?.call1((if value.is_none() { PyString::new(py, default).into_any() } else { @@ -146,7 +146,7 @@ fn plan_audit<'py>( "always_run", "description", ] { - values.set_item(field, audit.getattr(field)?)?; + values.set_item(field, native_field(&audit, field)?)?; } let measurement = native_field(&audit, "measurement_contract")?; for field in ["value_column", "sample_count_column", "sample_unit"] { @@ -155,7 +155,7 @@ fn plan_audit<'py>( if measurement.is_none() { py.None().into_bound(py) } else { - measurement.getattr(field)? + native_field(&measurement, field)? }, )?; } @@ -227,7 +227,7 @@ pub(crate) fn build_selected_audit_entries<'py>( let request = Request::new("build_selected_audit_entries", request)?; let py = request.py(); let project = request.get("project")?; - let selected_keys = request.get("scope")?.getattr("selected_keys")?; + let selected_keys = native_field(&request.get("scope")?, "selected_keys")?; let mut selected: Vec> = Vec::new(); for audit in native_field(&project, "audits")?.try_iter()? { let audit = audit?; 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 dcd33c1d73..f737702e6f 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 @@ -27,7 +27,7 @@ use crate::bindings::_helpers::references::source_relations::render_source_relat /// A model's authored config values. pub(crate) fn model_values<'py>(model: &Bound<'py, PyAny>) -> PyResult> { - native_field(model, "config")?.getattr("values") + native_field(&native_field(model, "config")?, "values") } /// Whether a model is incremental with a cursor column. @@ -107,7 +107,10 @@ pub(crate) fn reject_unexposed_cursor_input_columns<'py>( let warehouse_columns: &Bound<'py, PyAny> = &lookups.source_warehouse_columns; let model_name: String = model_reads::model_name(model)?; for role in ["filter_inputs", "watermark_inputs"] { - for item in roles.getattr(role)?.call_method0("items")?.try_iter()? { + for item in native_field(&roles, role)? + .call_method0("items")? + .try_iter()? + { let (input_name, column): (String, String) = item?.extract()?; let reference: Bound<'py, PyAny> = resolve_lineage_reference( model, 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 81d2f1d76c..f291797db3 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 @@ -71,9 +71,9 @@ pub(crate) fn planning_context( ("sources", "source_entry", SOURCE_REFERENCE_KIND, true), ("seeds", "schema_entry", SEED_REFERENCE_KIND, false), ] { - for resource in project.getattr(collection)?.try_iter()? { + for resource in native_field(project, collection)?.try_iter()? { let resource = resource?; - let entry = resource.getattr(entry)?; + let entry = native_field(&resource, entry)?; let columns: Vec = columns(&native_field(&entry, "columns")?, false)?; let authoritative = if source { native_field(&entry, "contract")?.eq("enforced")? diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_metadata.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_metadata.rs index 965a96576a..7034907f5e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_metadata.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/fixture_metadata.rs @@ -30,9 +30,7 @@ pub(crate) fn model_fixture_relation(model: &Bound<'_, PyAny>) -> PyResult = Vec::new(); - for key in model - .getattr("dep_rows")? - .extract::>()? - { + for key in native_field(model, "dep_rows")?.extract::>()? { if schema.iter().any(|relation| relation.key == key) { deps.push(key); } 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 6423ef4f64..e6aad3b0ab 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/lineage.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/lineage.rs @@ -48,7 +48,7 @@ fn references<'py>(owner: &Bound<'py, PyAny>) -> PyResult> /// Whether a model's `incremental_mode` is microbatch. fn microbatch_flag(model: &Bound<'_, PyAny>) -> PyResult { - let values: Bound<'_, PyAny> = native_field(model, "config")?.getattr("values")?; + let values: Bound<'_, PyAny> = native_field(&native_field(model, "config")?, "values")?; Ok(match config_str(&values, "incremental_mode")? { Some(mode) => mode.eq(MICROBATCH_MODE)?, None => false, @@ -57,8 +57,7 @@ fn microbatch_flag(model: &Bound<'_, PyAny>) -> PyResult { /// A model's watermark input names. fn watermark_names(model: &Bound<'_, PyAny>) -> PyResult> { - resolve_cursor_input_roles(model)? - .getattr("watermark_inputs")? + native_field(&resolve_cursor_input_roles(model)?, "watermark_inputs")? .try_iter()? .map(|name| text(&name?)) .collect() @@ -85,7 +84,8 @@ impl<'py> ReferenceGraph for ProjectLineage<'py> { fn producer_facts(&self, name: &str) -> PyResult { let producer: Bound<'py, PyAny> = self.models_by_name.get_item(name)?; - let values: Bound<'py, PyAny> = native_field(&producer, "config")?.getattr("values")?; + let values: Bound<'py, PyAny> = + native_field(&native_field(&producer, "config")?, "values")?; let capped: bool = match microbatch_limit_config(&values)?.1 { Some(action) => CAP_ACTIONS.contains(&text(&action)?.as_str()), None => false, 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 0ae7efdcdf..38ebd8a91b 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 @@ -204,9 +204,7 @@ fn default_limit<'py>( configured: Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = entry.py(); - if entry - .getattr("microbatch_strategy")? - .eq(ROLLING_WINDOW_STRATEGY)? + if native_field(entry, "microbatch_strategy")?.eq(ROLLING_WINDOW_STRATEGY)? && !native_field(entry, "reason")?.eq(FULL_REFRESH_REASON)? && native_field(entry, "start_cursor_override")?.is_none() && native_field(entry, "end_cursor_override")?.is_none() @@ -231,10 +229,7 @@ pub(crate) fn apply_configured_microbatch_limits<'py>( let py: Python<'py> = entry.py(); let configured: Bound<'py, PyAny> = native_field(inputs, "max_microbatches")?; let configured_action: Bound<'py, PyAny> = native_field(inputs, "microbatch_limit_action")?; - if inputs - .getattr("max_microbatches_is_override")? - .is_truthy()? - { + if native_field(inputs, "max_microbatches_is_override")?.is_truthy()? { let error: Bound<'py, PyAny> = enum_member(py, RecordId::MicrobatchLimitAction, "ERROR")?; return with_warnings(limited(entry, configured, error)?, Vec::new()); } 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 9dbe65f459..c1013e8b9b 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 @@ -511,8 +511,7 @@ pub(crate) fn assemble_model_plan_entry<'py>( let schema_actions: Bound<'py, PyAny> = resolve_schema_actions(&native_field(change, "schema_findings")?, &policy)?.into_any(); let enforcement: Bound<'py, PyAny> = type_enforcement(model)?; - let warehouse_columns: Bound<'py, PyAny> = snapshot - .getattr("existing_columns")? + let warehouse_columns: Bound<'py, PyAny> = native_field(snapshot, "existing_columns")? .call_method1("get", (&name, PyTuple::empty(py)))?; let warnings: Bound<'py, PyTuple> = model_warnings( request, @@ -522,11 +521,13 @@ pub(crate) fn assemble_model_plan_entry<'py>( let unique_key: Bound<'py, PyTuple> = string_list(&values, "unique_key")?; let merge_exclude: Bound<'py, PyTuple> = config_string_tuple(&values, "merge_exclude_columns")? .unwrap_or_else(|| PyTuple::empty(py)); - let table_type: Bound<'py, PyAny> = model - .getattr("config")? - .getattr("table_type")? - .getattr("value")? - .getattr("value")?; + let table_type: Bound<'py, PyAny> = native_field( + &native_field( + &native_field(&native_field(model, "config")?, "table_type")?, + "value", + )?, + "value", + )?; let ddl: String = logical_ddl( request, (&unique_key, &merge_exclude), @@ -541,7 +542,7 @@ pub(crate) fn assemble_model_plan_entry<'py>( entry.set_item("logical_ddl", table_type_ddl(&ddl, &text(&table_type)?))?; passed_fields(request, &entry)?; for field in CHANGE_FIELDS { - entry.set_item(field, change.getattr(field)?)?; + entry.set_item(field, native_field(change, field)?)?; } config_fields(request, &values, &entry)?; entry.set_item("unique_key", unique_key)?; @@ -552,10 +553,9 @@ pub(crate) fn assemble_model_plan_entry<'py>( contract_fields(model, &values, &entry)?; entry.set_item("pre_hooks", &request.hooks.0)?; entry.set_item("post_hooks", &request.hooks.1)?; - let fingerprint: Bound<'py, PyAny> = snapshot - .getattr("fingerprints")? - .getattr("models")? - .call_method1("get", (&name,))?; + let fingerprint: Bound<'py, PyAny> = + native_field(&native_field(snapshot, "fingerprints")?, "models")? + .call_method1("get", (&name,))?; entry.set_item( "previous_query_sql", if fingerprint.is_none() { 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 e268df43c0..2e7b609e08 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_strategy/values.rs @@ -40,10 +40,7 @@ pub(crate) fn config_value<'py>( model: &Bound<'py, PyAny>, key: &str, ) -> PyResult> { - model - .getattr("config")? - .getattr("values")? - .call_method1("get", (key,)) + native_field(&native_field(model, "config")?, "values")?.call_method1("get", (key,)) } /// The materialization type of a model's `materialized` config. @@ -107,21 +104,19 @@ pub(crate) fn finding_rows(findings: &Bound<'_, PyAny>) -> PyResult) -> PyResult { - let backfill: String = text(&native_field(change, "backfill")?.getattr("action")?)?; + let backfill: String = text(&native_field(&native_field(change, "backfill")?, "action")?)?; Ok(ChangeFacts { kind: change_kind(&text(&native_field(change, "change_kind")?)?), backfill_full: backfill == FULL_BACKFILL, backfill_forward_only: backfill == FORWARD_ONLY_BACKFILL, query_changed: native_field(change, "query_changed")?.is_truthy()?, - changed_functions: change - .getattr("changed_functions")? + changed_functions: native_field(change, "changed_functions")? .try_iter()? .map(|name| text(&name?)) .collect::>()?, config_changed: native_field(change, "config_changed")?.is_truthy()?, schema_findings: finding_rows(&native_field(change, "schema_findings")?)?, - recorded_build_relation_missing: change - .getattr("recorded_build_relation_missing")? + recorded_build_relation_missing: native_field(change, "recorded_build_relation_missing")? .is_truthy()?, }) } 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 64d3938452..9f436db537 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/cursors.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/cursors.rs @@ -195,10 +195,7 @@ fn runtime_producer_names( let mut loaded: HashSet = HashSet::new(); for source in native_field(project, "sources")?.try_iter()? { let source = source?; - if !native_field(&source, "source_entry")? - .getattr("loader")? - .is_none() - { + if !native_field(&native_field(&source, "source_entry")?, "loader")?.is_none() { loaded.insert(native_field(&source, "name")?.extract()?); } } @@ -224,8 +221,9 @@ fn upstream_relation<'py>( let name = native_field(reference, "ref_name")?; let name_text: String = name.extract()?; let is_selected = selected.is_some_and(|names| names.contains(&name_text)); - let qualified = - |node: Bound<'py, PyAny>| native_field(&node, "destination")?.getattr("qualified_name"); + let qualified = |node: Bound<'py, PyAny>| { + native_field(&native_field(&node, "destination")?, "qualified_name") + }; if (kind.eq("ref")? || kind.eq("seed")?) && !deferred.is_none() && deferred.contains(&name)? @@ -285,7 +283,10 @@ fn terminal_bound<'py>( }; let value = object( py, - config_cursor_bound(&native_field(&upstream, "config")?.getattr("values")?, key)?, + config_cursor_bound( + &native_field(&native_field(&upstream, "config")?, "values")?, + key, + )?, )?; if value.is_none() { return Ok(None); 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 5a321299ae..9f1cab5335 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 @@ -77,8 +77,7 @@ fn source_candidates<'py>( let mut names: BTreeSet<_> = BTreeSet::new(); for entry in &scoped { schemas.insert( - entry - .getattr("schema")? + native_field(entry, "schema")? .extract::>()? .unwrap_or_default(), ); @@ -182,10 +181,7 @@ pub(crate) fn semantic_source_columns<'py>( let selected_keys = &args.value("selected_keys")?; let py = project.py(); - if !project - .getattr("settings")? - .getattr("sql_analysis")? - .is_truthy()? + if !native_field(&native_field(project, "settings")?, "sql_analysis")?.is_truthy()? || connection.is_none() { return Ok(columns.clone()); @@ -193,9 +189,7 @@ pub(crate) fn semantic_source_columns<'py>( let mut required: BTreeSet = BTreeSet::new(); for model in native_field(project, "models")?.try_iter()? { let model = model?; - let analysis = model - .getattr("config")? - .getattr("values")? + let analysis = native_field(&native_field(&model, "config")?, "values")? .call_method1("get", ("sql_analysis",))?; if !selected_keys.contains(model_reads::model_key(&model)?)? || analysis.is(pyo3::types::PyBool::new(py, false)) 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 b588fec416..a31ce1cf1c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/snapshot.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/snapshot.rs @@ -72,7 +72,7 @@ pub(crate) fn destination_nodes<'py>( ) -> PyResult>> { let mut nodes: Vec<_> = Vec::new(); for field in ["models", "seeds", "functions"] { - for node in project.getattr(field)?.try_iter()? { + for node in native_field(project, field)?.try_iter()? { nodes.push(node?); } } @@ -103,7 +103,7 @@ fn resolve_database<'py>( if !selected_node(selected, &node)? { continue; } - let database = native_field(&node, "destination")?.getattr("database")?; + let database = native_field(&native_field(&node, "destination")?, "database")?; if !database.is_none() { return Ok(database); } @@ -120,7 +120,7 @@ fn target_schemas( if !selected_node(selected, &node)? { continue; } - let schema = native_field(&node, "destination")?.getattr("schema")?; + let schema = native_field(&native_field(&node, "destination")?, "schema")?; if !schema.is_none() { schemas.insert(schema.extract()?); } @@ -157,11 +157,7 @@ fn model_upstream_names( } let lookup = if is_ref { models } else { seeds }; if let Some(node) = lookup.get_item(&name)? { - names.insert( - native_field(&node, "destination")? - .getattr("name")? - .extract()?, - ); + names.insert(native_field(&native_field(&node, "destination")?, "name")?.extract()?); } } Ok(names) @@ -175,11 +171,9 @@ fn metadata_name_filter( let mut names: BTreeSet = BTreeSet::new(); if selected.is_none() { for field in ["models", "seeds"] { - for node in project.getattr(field)?.try_iter()? { + for node in native_field(project, field)?.try_iter()? { names.insert( - native_field(&node?, "destination")? - .getattr("name")? - .extract()?, + native_field(&native_field(&node?, "destination")?, "name")?.extract()?, ); } } @@ -209,11 +203,8 @@ fn metadata_name_filter( &selected_names, )?); } else if let Some(seed) = seeds.get_item(&name)? { - names.insert( - native_field(&seed, "destination")? - .getattr("name")? - .extract()?, - ); + names + .insert(native_field(&native_field(&seed, "destination")?, "name")?.extract()?); } else if let Some(entry) = sources.get_item(&name)? { names.insert(source_table_name(&entry)?); } @@ -357,7 +348,7 @@ fn optional_names<'py>( fn location_identity<'py>(location: &Bound<'py, PyAny>) -> PyResult> { let py = location.py(); let part = |name: &str| -> PyResult> { - let value = location.getattr(name)?; + let value = native_field(location, name)?; if value.is_none() { Ok(value) } else { @@ -480,9 +471,7 @@ fn shares_unfiltered(project: &Bound<'_, PyAny>, selected: &Bound<'_, PyAny>) -> if !names.contains(&model_reads::model_name(&model)?) { all_selected = false; } - if model - .getattr("config")? - .getattr("values")? + if native_field(&native_field(&model, "config")?, "values")? .call_method1("get", (MIGRATE_FROM_CONFIG_KEY,))? .is_instance_of::() { @@ -554,9 +543,7 @@ fn gather_fingerprints<'py>( ), ( "render_read_latest_sql", - request - .adapter - .getattr("render_read_latest_fingerprints_sql")?, + native_field(&request.adapter, "render_read_latest_fingerprints_sql")?, ), ( "node_names", @@ -634,7 +621,7 @@ fn scope_field<'py>(scope: &Bound<'py, PyAny>, name: &str) -> PyResult( ) -> PyResult>> { match source_entries.filter(|entries| !entries.is_none()) { Some(entries) => entries.try_iter()?.collect(), - None => project - .getattr("sources")? + None => native_field(project, "sources")? .try_iter()? .map(|source| native_field(&source?, "source_entry")) .collect(), @@ -110,8 +109,7 @@ pub(crate) fn source_inspection<'py>( if schema.is_none() { continue; } - let key: String = entry - .getattr("database")? + let key: String = native_field(entry, "database")? .extract::>()? .unwrap_or_default(); let schema: String = schema.extract()?; 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 cbd81e0167..3563b6c80a 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 @@ -52,7 +52,7 @@ fn hook_source_reads( let py = model.py(); let hook_entry = record_class(py, SQL_HOOK_ENTRY)?; let source_kind = host::entry(py, UserCodeHost::SqlResourceRefKind)?.getattr("SOURCE")?; - let values = native_field(model, "config")?.getattr("values")?; + let values = native_field(&native_field(model, "config")?, "values")?; for key in MODEL_HOOK_CONFIG_KEYS { let value = values.call_method1("get", (key,))?; let entries: Vec> = @@ -158,8 +158,10 @@ fn source_target_name<'py>( if active.is_none() || project_config.is_none() || local_config.is_none() { return Ok(py.None().into_bound(py)); } - let deferred = resolve_target(project_config, local_config, active.clone())? - .getattr("defer_sources_to")?; + let deferred = native_field( + &resolve_target(project_config, local_config, active.clone())?, + "defer_sources_to", + )?; Ok(if deferred.is_truthy()? { deferred } else { @@ -170,10 +172,8 @@ fn source_target_name<'py>( fn raw_source_entry<'py>(source: &Bound<'py, PyAny>) -> PyResult> { let declared = native_field(source, "source_entry")?; let name = native_field(&declared, "name")?; - for entry in source - .getattr("source_file")? - .getattr("source_entries")? - .try_iter()? + for entry in + native_field(&native_field(source, "source_file")?, "source_entries")?.try_iter()? { let entry = entry?; if native_field(&entry, "name")?.eq(&name)? { @@ -309,9 +309,8 @@ pub(crate) fn build_source_read_map<'py>( let result = source_map.copy()?; for source in native_field(project, "sources")?.try_iter()? { let source = source?; - let name: String = native_field(&source, "source_entry")? - .getattr("name")? - .extract()?; + let name: String = + native_field(&native_field(&source, "source_entry")?, "name")?.extract()?; if !selected.contains(&name) { continue; } 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 1da8bab148..bcaba41d37 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 @@ -31,8 +31,7 @@ pub(crate) fn source_freshness_unknown_warnings<'py>( ) -> PyResult> { let py = source_freshness.py(); let mut reasons: Vec<(String, Vec)> = Vec::new(); - for unknown in source_freshness - .getattr("unknown_sources")? + for unknown in native_field(source_freshness, "unknown_sources")? .call_method0("values")? .try_iter()? { @@ -69,7 +68,7 @@ pub(crate) fn source_freshness_unknown_warnings<'py>( fn state_database<'py>(project: &Bound<'py, PyAny>) -> PyResult> { for node in destination_nodes(project)? { - let database = native_field(&node, "destination")?.getattr("database")?; + let database = native_field(&native_field(&node, "destination")?, "database")?; if !database.is_none() { return Ok(database); } @@ -83,7 +82,7 @@ fn state_schemas( ) -> PyResult> { let mut schemas: BTreeSet<_> = BTreeSet::new(); for node in destination_nodes(project)? { - let schema = native_field(&node, "destination")?.getattr("schema")?; + let schema = native_field(&native_field(&node, "destination")?, "schema")?; if !schema.is_none() { schemas.insert(schema.extract()?); } @@ -152,8 +151,7 @@ pub(crate) fn build_planner_source_freshness_result<'py>( } let sources = tuple( py, - relations - .getattr("source_read_map")? + native_field(relations, "source_read_map")? .call_method0("values")? .try_iter()? .collect::>()?, 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 4e05137381..864bbce797 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 @@ -73,8 +73,7 @@ fn read_relation<'py>( fn declaration_location(source: &Bound<'_, PyAny>) -> PyResult { let file = native_field(source, "source_file")?; - let path: String = file - .getattr("relative_path")? + let path: String = native_field(&file, "relative_path")? .call_method0("as_posix")? .extract()?; let line = yaml_entry_line( 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 7e6a3dd1f9..d1607f2e4a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/watermarks.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_warehouse/watermarks.rs @@ -91,7 +91,7 @@ pub(crate) fn validate_watermark_column<'py>( (Some(WATERMARK_CONTRACT_CODE), None), )); } - let values = native_field(&upstream, "config")?.getattr("values")?; + let values = native_field(&native_field(&upstream, "config")?, "values")?; if !values.call_method1("get", ("contract",))?.eq("enforced")? { return Ok(()); } @@ -114,7 +114,10 @@ pub(crate) fn validate_watermark_column<'py>( let Some(seed) = seeds.get_item(&ref_name)? else { return Ok(()); }; - declared_columns(&native_field(&seed, "schema_entry")?.getattr("columns")?)? + declared_columns(&native_field( + &native_field(&seed, "schema_entry")?, + "columns", + )?)? } else { return Ok(()); }; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/project_assembly/declared_relations.rs b/crates/sqlbuild-python/src/bindings/_helpers/project_assembly/declared_relations.rs index baff313cf2..e2c2c9a375 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/project_assembly/declared_relations.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/project_assembly/declared_relations.rs @@ -90,11 +90,10 @@ pub(crate) fn declared_relations<'py>( (schema.name, attached_columns(&model)?); types.set_item(&name, column_types(py, &columns, false)?)?; nulls.set_item(&name, column_nullability(&columns, &nullability)?)?; - let contract: Option> = model - .getattr("config")? - .getattr("values")? - .downcast_into::()? - .get_item(CONTRACT_KEY)?; + let contract: Option> = + native_field(&native_field(&model, "config")?, "values")? + .downcast_into::()? + .get_item(CONTRACT_KEY)?; let enforced: bool = match contract { Some(contract) => contract.eq(ENFORCED_CONTRACT)?, None => false, 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 93de569bf1..2386aa0130 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 @@ -123,7 +123,7 @@ fn variables<'py>(effective_vars: &Bound<'py, PyAny>) -> PyResult, attribute: &str) -> PyResult> { - value.getattr(attribute)?.extract() + native_field(value, attribute)?.extract() } /// Python's own `os.environ` lookup of every name an `ENV:` template may read. @@ -183,19 +183,12 @@ fn model_facts(model_input: &Bound<'_, PyAny>, checks: &Bound<'_, PyAny>) -> PyR syntax_checks.push(SyntaxCheck { sql, placeholders }); } Ok(ModelFacts { - name: model_file - .getattr("file_path")? - .getattr("stem")? - .extract()?, - directory: model_file - .getattr("relative_path")? - .getattr("parent")? + name: native_field(&native_field(&model_file, "file_path")?, "stem")?.extract()?, + directory: native_field(&native_field(&model_file, "relative_path")?, "parent")? .str()? .to_string(), tags: tags( - &model_input - .getattr("config")? - .getattr("values")? + &native_field(&native_field(model_input, "config")?, "values")? .call_method1("get", ("tags",))?, )?, references: references(reference_rows), @@ -205,7 +198,7 @@ fn model_facts(model_input: &Bound<'_, PyAny>, checks: &Bound<'_, PyAny>) -> PyR /// Every item of `inputs.`. fn items<'py>(inputs: &Bound<'py, PyAny>, attribute: &str) -> PyResult>> { - inputs.getattr(attribute)?.try_iter()?.collect() + native_field(inputs, attribute)?.try_iter()?.collect() } /// The native assembly request for compile inputs, and the exceptions opaque vars raise. @@ -227,7 +220,7 @@ fn project_request<'py>( }) }; let defaults: Bound<'py, PyAny> = - native_field(inputs, "project_config")?.getattr("defaults")?; + native_field(&native_field(inputs, "project_config")?, "defaults")?; let defaults = SeedDefaults { database: optional_text(&defaults, "database")?, schema: optional_text(&defaults, "schema")?, @@ -240,8 +233,7 @@ fn project_request<'py>( let entry: Bound<'py, PyAny> = native_field(seed_input, "schema_entry")?; seeds.push(SeedFacts { name: native_field(&entry, "name")?.extract()?, - tags: entry - .getattr("tags")? + tags: native_field(&entry, "tags")? .try_iter()? .map(|tag| tag?.extract()) .collect::>()?, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/project_assembly/syntax_checks.rs b/crates/sqlbuild-python/src/bindings/_helpers/project_assembly/syntax_checks.rs index cf8b344cf6..cd5f88fbe5 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/project_assembly/syntax_checks.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/project_assembly/syntax_checks.rs @@ -13,18 +13,15 @@ use crate::bindings::_helpers::records::fields::field as native_field; const HOOK_NAMES: [&str; 2] = ["pre_hooks", "post_hooks"]; fn config_value<'py>(model_input: &Bound<'py, PyAny>, key: &str) -> PyResult> { - model_input - .getattr("config")? - .getattr("values")? - .call_method1("get", (key,)) + native_field(&native_field(model_input, "config")?, "values")?.call_method1("get", (key,)) } fn model_name(model_input: &Bound<'_, PyAny>) -> PyResult { - model_input - .getattr("model_file")? - .getattr("file_path")? - .getattr("stem")? - .extract() + native_field( + &native_field(&native_field(model_input, "model_file")?, "file_path")?, + "stem", + )? + .extract() } /// The model's `@@@name` placeholder defaults as strings, or `None`. 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 8576553bc6..cbc1c175ed 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/query_command/dag.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/query_command/dag.rs @@ -66,8 +66,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 = build_dag_json( &graph, - &native_field(&discovered, "project_config")? - .getattr("name")? + &native_field(&native_field(&discovered, "project_config")?, "name")? .extract::()?, Some(&python_graph), )?; 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 f566c1853a..2f98ba8776 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/query_command/query.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/query_command/query.rs @@ -204,8 +204,7 @@ fn run_sql<'py>( } fn rows<'py>(result: &Bound<'py, PyAny>) -> PyResult>>> { - result - .getattr("rows")? + native_field(result, "rows")? .try_iter()? .map(|row| row?.try_iter()?.collect::>>()) .collect() @@ -248,8 +247,7 @@ fn render_json( /// The result in the requested format; a statement without columns prints `OK`. fn render(result: &Bound<'_, PyAny>, output_format: &str, limit: Option) -> PyResult { let py: Python<'_> = result.py(); - let columns: Vec = result - .getattr("columns")? + let columns: Vec = native_field(result, "columns")? .try_iter()? .map(|column| column?.extract()) .collect::>()?; 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 614ffa5c93..c164b65794 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/compiled.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/compiled.rs @@ -194,7 +194,7 @@ pub(crate) fn finish_sql_test( let parent: Bound<'_, PyAny> = if block_name.is_truthy()? { block_name } else { - native_field(&test_file, "relative_path")?.getattr("stem")? + native_field(&native_field(&test_file, "relative_path")?, "stem")? }; values[TEST_PARENT_NAME_FIELD] = parent.unbind(); } @@ -224,7 +224,7 @@ pub(crate) fn record_reference_rows(instance: &Bound<'_, PyAny>) -> PyResult, name: String) -> PyResult> { - let values: Bound<'_, PyAny> = native_field(instance, "config")?.getattr("values")?; + let values: Bound<'_, PyAny> = native_field(&native_field(instance, "config")?, "values")?; entry_relation_key_row( "ref", name, @@ -236,11 +236,11 @@ fn model_relation_key_row(instance: &Bound<'_, PyAny>, name: String) -> PyResult /// `CompileModelInput.relation_key_row`: keyed by the model file's stem. pub(crate) fn input_relation_key_row(instance: &Bound<'_, PyAny>) -> PyResult> { - let name: String = instance - .getattr("model_file")? - .getattr("file_path")? - .getattr("stem")? - .extract()?; + let name: String = native_field( + &native_field(&native_field(instance, "model_file")?, "file_path")?, + "stem", + )? + .extract()?; model_relation_key_row(instance, name) } 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 03755a8b86..e98819a728 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/cursors.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/cursors.rs @@ -128,9 +128,7 @@ pub(crate) fn finish_cursor_snapshot( /// `ModelCursorSnapshot.watermarks_available`: whether every physical watermark was read. pub(crate) fn snapshot_watermarks_available(instance: &Bound<'_, PyAny>) -> PyResult> { - let available: bool = !instance - .getattr("unavailable_watermark_tags")? - .is_truthy()?; + let available: bool = !native_field(instance, "unavailable_watermark_tags")?.is_truthy()?; Ok(pyo3::types::PyBool::new(instance.py(), available) .to_owned() .into_any() diff --git a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/discovery.rs b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/discovery.rs index bf2008d88c..b9eab8efd4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/discovery.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/discovery.rs @@ -16,10 +16,7 @@ const SCALAR_TYPE_LABELS: [(&str, &str); 6] = [ /// `ConstantDeclaration.logical_type`: the normalized logical type. pub(crate) fn constant_logical_type(declaration: &Bound<'_, PyAny>) -> PyResult> { - Ok(declaration - .getattr("value")? - .getattr("logical_type")? - .unbind()) + Ok(native_field(&native_field(declaration, "value")?, "logical_type")?.unbind()) } /// `ConstantDeclaration.scalar_type`: the SQL scalar type label, or the upper-cased logical type. @@ -29,9 +26,7 @@ pub(crate) fn constant_scalar_type(declaration: &Bound<'_, PyAny>) -> PyResult

( } fn part(duration: &Bound<'_, PyAny>, name: &str) -> PyResult { - duration.getattr(name)?.extract() + native_field(duration, name)?.extract() } fn total_months(duration: &Bound<'_, PyAny>) -> PyResult { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/error_inits.rs b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/error_inits.rs index 5d07014474..5bba071938 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/error_inits.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/error_inits.rs @@ -104,7 +104,7 @@ fn macro_argument_copy<'py>( if name == LOCATION_FIELD { continue; } - keywords.set_item(name, error.getattr(name)?)?; + keywords.set_item(name, native_field(error, name)?)?; } let help: Bound<'py, PyAny> = native_field(error, "help")?; if !help.is_truthy()? { 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 a7a3cccbfa..16443fb97d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/freshness.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/freshness.rs @@ -1,10 +1,11 @@ //! Validation and identity of the natively hosted source freshness state record. -use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyResult, Python}; +use pyo3::prelude::{Bound, Py, PyAny, PyResult, Python}; use pyo3::types::PyDict; use crate::bindings::_helpers::freshness::observation::require_aware_utc_datetime; use crate::bindings::_helpers::records::construct::new_record; +use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; /// `SourceFreshnessRecord.observed_at` position. @@ -32,7 +33,7 @@ pub(crate) fn freshness_record_identity(instance: &Bound<'_, PyAny>) -> PyResult let py: Python<'_> = instance.py(); let keywords: Bound<'_, PyDict> = PyDict::new(py); for name in IDENTITY_FIELDS { - pyo3::types::PyDictMethods::set_item(&keywords, name, instance.getattr(name)?)?; + pyo3::types::PyDictMethods::set_item(&keywords, name, native_field(instance, name)?)?; } Ok(new_record(py, RecordId::SourceFreshnessIdentity, (), 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 92ca032d2c..3d65e47dca 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/lexical.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/lexical.rs @@ -29,7 +29,7 @@ fn sorted_texts(values: &Bound<'_, PyAny>) -> PyResult> { /// The lexical rules a `SqlLexicalSyntax` record holds. fn record_syntax(instance: &Bound<'_, PyAny>) -> PyResult { - let flag = |name: &str| -> PyResult { instance.getattr(name)?.is_truthy() }; + let flag = |name: &str| -> PyResult { native_field(instance, name)?.is_truthy() }; Ok(LexicalSyntax { backslash_escape_quotes: sorted_texts(&native_field(instance, "backslash_escape_quotes")?)?, escape_string_prefix: flag("escape_string_prefix")?, 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 f3c294064f..d4cab8b10a 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 @@ -54,9 +54,8 @@ fn resource_name<'py>( if !name.is_none() { return Ok(name); } - let pooled: Bound<'py, PyAny> = facts - .getattr("resource_name_indexes")? - .call_method1("get", (index, index))?; + let pooled: Bound<'py, PyAny> = + native_field(facts, "resource_name_indexes")?.call_method1("get", (index, index))?; native_field(facts, "string_pool")?.get_item(pooled) } @@ -118,8 +117,7 @@ fn project_row(facts: &Bound<'_, PyAny>, row: &Bound<'_, PyAny>) -> PyResult) -> PyResult> { - let projected: Vec> = instance - .getattr("rows")? + let projected: Vec> = native_field(instance, "rows")? .try_iter()? .map(|row| project_row(instance, &row?)) .collect::>()?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/macros.rs b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/macros.rs index 9acd116b41..7e6704149a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/macros.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/macros.rs @@ -23,8 +23,7 @@ pub(crate) fn macro_injects_context(instance: &Bound<'_, PyAny>) -> PyResult = py.import("inspect")?.into_any(); let parameters: Bound<'_, PyTuple> = PyTuple::new( py, - inspect - .getattr("signature")? + native_field(&inspect, "signature")? .call1((native_field(instance, "function")?,))? .getattr("parameters")? .call_method0("values")? @@ -59,9 +58,8 @@ pub(crate) fn context_render_constant( )?); } let _ = native_field(instance, "constants")?.get_item(&name)?; - let declaration: Bound<'_, PyAny> = instance - .getattr("_constant_declarations")? - .call_method1("get", (&name,))?; + let declaration: Bound<'_, PyAny> = + native_field(instance, "_constant_declarations")?.call_method1("get", (&name,))?; if declaration.is_none() { return Err(compile_input_error( py, @@ -89,8 +87,7 @@ pub(crate) fn context_render_enum_member( } let enum_name: Bound<'_, PyAny> = call_argument((args, kwargs), "enum_name", 0)?; let member_name: Bound<'_, PyAny> = call_argument((args, kwargs), "member_name", 1)?; - let value: Bound<'_, PyAny> = instance - .getattr("enums")? + let value: Bound<'_, PyAny> = native_field(instance, "enums")? .get_item(enum_name)? .get_item(member_name)?; render_enum_member_value(&value)?.into_py_any(instance.py()) @@ -146,13 +143,9 @@ fn resource_rows( /// A model's configured tags and folder. fn model_tags(model: &Bound<'_, PyAny>) -> PyResult { - let tags: Bound<'_, PyAny> = model - .getattr("config")? - .getattr("values")? - .call_method1("get", ("tags",))?; - let folder: String = model - .getattr("relative_path")? - .getattr("parent")? + let tags: Bound<'_, PyAny> = + native_field(&native_field(model, "config")?, "values")?.call_method1("get", ("tags",))?; + let folder: String = native_field(&native_field(model, "relative_path")?, "parent")? .str()? .extract()?; Ok((graph_tags(&tags)?, Some(folder))) @@ -165,9 +158,7 @@ fn source_tags(_source: &Bound<'_, PyAny>) -> PyResult { /// A seed's schema entry tags. fn seed_tags(seed: &Bound<'_, PyAny>) -> PyResult { - let tags: Vec = seed - .getattr("schema_entry")? - .getattr("tags")? + let tags: Vec = native_field(&native_field(seed, "schema_entry")?, "tags")? .try_iter()? .map(|tag| tag?.extract()) .collect::>()?; @@ -176,8 +167,7 @@ fn seed_tags(seed: &Bound<'_, PyAny>) -> PyResult { /// A function's tags. fn function_tags(function: &Bound<'_, PyAny>) -> PyResult { - let tags: Vec = function - .getattr("tags")? + let tags: Vec = native_field(function, "tags")? .try_iter()? .map(|tag| tag?.extract()) .collect::>()?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/migrations.rs b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/migrations.rs index dcfa74561a..019a336fb0 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/migrations.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/migrations.rs @@ -199,10 +199,10 @@ pub(crate) fn history_status( /// `OldNameViewHistory.old`: the relation name the compatibility view occupies. pub(crate) fn history_old(history: &Bound<'_, PyAny>) -> PyResult> { - Ok(native_field(history, "required")?.getattr("old")?.unbind()) + Ok(native_field(&native_field(history, "required")?, "old")?.unbind()) } /// `OldNameViewHistory.new`: the relation the compatibility view reads. pub(crate) fn history_new(history: &Bound<'_, PyAny>) -> PyResult> { - Ok(native_field(history, "required")?.getattr("new")?.unbind()) + Ok(native_field(&native_field(history, "required")?, "new")?.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 14e7bdf498..9d23534fc3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/planner.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/planner.rs @@ -60,7 +60,7 @@ pub(crate) fn old_name_runs_steps(instance: &Bound<'_, PyAny>) -> PyResult) -> PyResult> { for name in ["python_source_read_map", "source_read_map"] { - let entries: Bound<'_, PyAny> = instance.getattr(name)?; + let entries: Bound<'_, PyAny> = native_field(instance, name)?; if entries.is_truthy()? { return Ok(entries.unbind()); } @@ -265,7 +265,7 @@ pub(crate) fn overrides_typed_as( return Ok(instance.clone().unbind()); } let cursor_type: String = text(&call_argument((args, kwargs), "cursor_type", 0)?)?; - let field = |name: &str| instance.getattr(name); + let field = |name: &str| native_field(instance, name); let (start, end) = (field(OVERRIDE_FIELDS[4])?, field(OVERRIDE_FIELDS[5])?); let keywords: Bound<'_, PyDict> = PyDict::new(py); if cursor_type == INTEGER_CURSOR { @@ -331,8 +331,7 @@ pub(crate) fn rename_hint_message(hint: &Bound<'_, PyAny>) -> PyResult } else { "similar to" }; - let candidates: Vec = hint - .getattr("candidate_columns")? + let candidates: Vec = native_field(hint, "candidate_columns")? .try_iter()? .map(|candidate| Ok(candidate?.str()?.to_string())) .collect::>()?; 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 feb5c65d90..deab03422d 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 @@ -39,16 +39,14 @@ pub(crate) fn project_path_depth(instance: &Bound<'_, PyAny>) -> PyResult) -> PyResult> { let project: Bound<'_, PyAny> = enum_member(instance.py(), RecordId::RuleSubject, "PROJECT")?; - instance - .getattr("subject")? + native_field(instance, "subject")? .is(&project) .into_py_any(instance.py()) } /// `RuleResult.finding_count`. pub(crate) fn rule_result_finding_count(instance: &Bound<'_, PyAny>) -> PyResult> { - instance - .getattr("findings")? + native_field(instance, "findings")? .len()? .into_py_any(instance.py()) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/scopes.rs b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/scopes.rs index 4778b7c67b..2e33a0e9d5 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/scopes.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/scopes.rs @@ -18,7 +18,7 @@ const COMPLETENESS_SECTIONS: [&str; 6] = [ pub(crate) fn completeness_complete(instance: &Bound<'_, PyAny>) -> PyResult> { let mut complete: bool = true; for section in COMPLETENESS_SECTIONS { - complete = complete && instance.getattr(section)?.is_truthy()?; + complete = complete && native_field(instance, section)?.is_truthy()?; } complete.into_py_any(instance.py()) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/values.rs b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/values.rs index 2a836823f1..29ff62861c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/record_behaviours/values.rs @@ -7,9 +7,7 @@ use pyo3::types::{PyDict, PyDictMethods, PyTuple}; /// `SqlLogicalType.display_name`: the kind, with its element type in angle brackets. pub(crate) fn logical_type_display_name(instance: &Bound<'_, PyAny>) -> PyResult> { - let kind: String = native_field(instance, "kind")? - .getattr("value")? - .extract()?; + let kind: String = native_field(&native_field(instance, "kind")?, "value")?.extract()?; let element: Bound<'_, PyAny> = native_field(instance, "element_type")?; if element.is_none() { return kind.into_py_any(instance.py()); @@ -20,9 +18,7 @@ pub(crate) fn logical_type_display_name(instance: &Bound<'_, PyAny>) -> PyResult /// `SqlValue.kind`: the outer logical kind. pub(crate) fn sql_value_kind(instance: &Bound<'_, PyAny>) -> PyResult> { - Ok(native_field(instance, "logical_type")? - .getattr("kind")? - .unbind()) + Ok(native_field(&native_field(instance, "logical_type")?, "kind")?.unbind()) } /// `FingerprintSet`: index the fingerprints by `(node_type, node_name)` when not given. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/records/fields.rs b/crates/sqlbuild-python/src/bindings/_helpers/records/fields.rs index c462f7d879..f1dfd120ca 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/records/fields.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/records/fields.rs @@ -60,11 +60,20 @@ pub(crate) fn replaced<'py>( value: &Bound<'py, PyAny>, changes: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - let py: Python<'py> = value.py(); - let keywords: Bound<'py, PyDict> = PyDict::new(py); + let keywords: Bound<'py, PyDict> = PyDict::new(value.py()); for (name, changed) in changes { keywords.set_item(*name, changed)?; } + replaced_keywords(value, &keywords) +} + +/// `dataclasses.replace(value, **keywords)`: a native record is rebuilt from its own values. +pub(crate) fn replaced_keywords<'py>( + value: &Bound<'py, PyAny>, + keywords: &Bound<'py, PyDict>, +) -> PyResult> { + let py: Python<'py> = value.py(); + let keywords: Bound<'py, PyDict> = keywords.copy()?; let Ok(record) = value.cast::() else { return imported(py, "dataclasses", "replace")?.call((value,), Some(&keywords)); }; 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 fb6e1d5241..4d7ebe0aaf 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/refactor/contract_compare.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/refactor/contract_compare.rs @@ -12,10 +12,10 @@ use crate::bindings::_helpers::native_host::adapter::AdapterHost; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::records::construct::{enum_from_value, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced; use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::type_system::equality::types_equal as native_types_equal; -const DATACLASSES_MODULE: &str = "dataclasses"; const MODEL_RESOURCE: &str = "model"; const SOURCE_RESOURCE: &str = "source"; @@ -23,7 +23,6 @@ const SOURCE_RESOURCE: &str = "source"; struct Vocabulary<'py> { finding: Bound<'py, PyAny>, kinds: Bound<'py, PyAny>, - replace: Bound<'py, PyAny>, } impl<'py> Vocabulary<'py> { @@ -31,7 +30,6 @@ impl<'py> Vocabulary<'py> { Ok(Self { finding: record_class(py, RecordId::ContractFinding)?, kinds: record_class(py, RecordId::ContractFindingKind)?, - replace: py.import(DATACLASSES_MODULE)?.getattr("replace")?, }) } @@ -62,11 +60,7 @@ impl<'py> Vocabulary<'py> { item: &Bound<'py, PyAny>, fields: Vec<(&str, Bound<'py, PyAny>)>, ) -> PyResult> { - let keywords: Bound<'py, PyDict> = PyDict::new(self.py()); - for (name, value) in fields { - keywords.set_item(name, value)?; - } - self.replace.call((item,), Some(&keywords)) + replaced(item, &fields) } } @@ -89,7 +83,7 @@ fn keyword_call<'py>( fn qualified(item: &Bound<'_, PyAny>) -> PyResult { let mut parts: Vec = Vec::new(); for name in ["database", "schema", "relation"] { - let part: Option = item.getattr(name)?.extract()?; + let part: Option = native_field(item, name)?.extract()?; if let Some(part) = part.filter(|part| !part.is_empty()) { parts.push(part); } @@ -200,7 +194,7 @@ fn source_evidence<'py>( ("physical_columns", py.None().into_bound(py)), ( "source_path", - native_field(source, "source_file")?.getattr("relative_path")?, + native_field(&native_field(source, "source_file")?, "relative_path")?, ), ("findings", PyTuple::new(py, findings)?.into_any()), ], @@ -372,8 +366,7 @@ fn compared<'py>( vec![("findings", PyTuple::new(py, [finding])?.into_any())], ); } - let declared: Vec> = item - .getattr("declared_columns")? + let declared: Vec> = native_field(item, "declared_columns")? .try_iter()? .collect::>()?; let physical: Vec> = physical_columns.try_iter()?.collect::>()?; 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 e2e98015d3..fc57be5300 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/refactor/contract_edits.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/refactor/contract_edits.rs @@ -123,16 +123,10 @@ fn edit_contract_source( overwrite: bool, contents: Option, ) -> PyResult<(Option, Option)> { - let discovered_contents: String = source - .getattr("source_file")? - .getattr("contents")? - .extract()?; + let discovered_contents: String = + native_field(&native_field(source, "source_file")?, "contents")?.extract()?; let mut columns: Vec<(String, Option)> = Vec::new(); - for column in source - .getattr("source_entry")? - .getattr("columns")? - .try_iter()? - { + for column in native_field(&native_field(source, "source_entry")?, "columns")?.try_iter()? { let column: Bound<'_, PyAny> = column?; columns.push(( native_field(&column, "name")?.extract()?, 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 a17665559b..a4c6bef6a6 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/refactor/offline_compile.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/refactor/offline_compile.rs @@ -171,7 +171,7 @@ pub(crate) fn compile_for_refactor<'py>( ("adapter", native_field(&analysis, "adapter")?), ( "project", - native_field(&analysis, "graph")?.getattr("project")?, + native_field(&native_field(&analysis, "graph")?, "project")?, ), ], )?), 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 6da26b86e8..ffd72ff179 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/refactor/python_strings.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/refactor/python_strings.rs @@ -31,7 +31,7 @@ fn python_paths<'py>( ) -> PyResult, Bound<'py, PyAny>)>> { let mut paths: Vec<(Vec, Bound<'py, PyAny>)> = Vec::new(); for declarations in PYTHON_DECLARATIONS { - for item in discovered.getattr(declarations)?.try_iter()? { + for item in native_field(discovered, declarations)?.try_iter()? { let path: Bound<'py, PyAny> = native_field(&item?, "relative_path")?; let parts: Vec = native_field(&path, "parts")?.extract()?; if !paths.iter().any(|(known, _)| *known == parts) { 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 938af86448..00517fea1d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/refactor/refactor_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/refactor/refactor_command.rs @@ -305,8 +305,10 @@ fn run(py: Python<'_>, command: &Command<'_>, status: &Status<'_>) -> PyResult = - target_lookup(&native_field(&before.project, "graph")?.getattr("all_keys")?)?; + let names: HashMap = target_lookup(&native_field( + &native_field(&before.project, "graph")?, + "all_keys", + )?)?; let lookup = |name: &str| names.get(name).cloned(); let request: RefactorRequest = refactor_request_for_target(&target_request(&command.request)?, &lookup) 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 bb51e1dc26..df38754cff 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/refactor/refactor_facts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/refactor/refactor_facts.rs @@ -57,7 +57,7 @@ fn resource_columns( let resource: Bound<'_, PyAny> = resource?; facts.push(ResourceColumnsFacts { name: native_field(&resource, "name")?.extract()?, - columns: column_names(&native_field(&resource.getattr(entry)?, "columns")?)?, + columns: column_names(&native_field(&native_field(&resource, entry)?, "columns")?)?, }); } Ok(facts) @@ -80,8 +80,7 @@ fn spans(spans: &Bound<'_, PyAny>) -> PyResult> { fn expansion_facts(expansion: &Bound<'_, PyAny>) -> PyResult { Ok(ExpansionFacts { expanded_sql: native_field(expansion, "expanded_sql")?.extract()?, - passes: expansion - .getattr("passes")? + passes: native_field(expansion, "passes")? .try_iter()? .map(|pass| spans(&pass?)) .collect::>()?, @@ -118,7 +117,7 @@ fn model(model: &Bound<'_, PyAny>, expansion: Option<&Bound<'_, PyAny>>) -> PyRe let values: Bound<'_, PyDict> = PyDict::new(py); values.call_method1( "update", - (native_field(model, "config")?.getattr("values")?,), + (native_field(&native_field(model, "config")?, "values")?,), )?; let materialized: Option = config_str(&values, MATERIALIZED_KEY)? .map(|value| value.extract()) @@ -165,11 +164,11 @@ pub(crate) fn model_facts( if refactor_project.is_none() { return Ok(None); } - let project: Bound<'_, PyAny> = native_field(refactor_project, "graph")?.getattr("project")?; + let project: Bound<'_, PyAny> = + native_field(&native_field(refactor_project, "graph")?, "project")?; let project_dir: Bound<'_, PyAny> = native_field(refactor_project, "project_dir")?; let mut expansions: HashMap> = HashMap::new(); - let items: Vec> = project - .getattr("sql_expansions")? + let items: Vec> = native_field(&project, "sql_expansions")? .call_method0("items")? .try_iter()? .collect::>()?; @@ -218,10 +217,10 @@ fn authored_files(discovered: &Bound<'_, PyAny>) -> PyResult> ]; let mut files: Vec = Vec::new(); for (role, attribute, body, blocks) in groups { - for item in discovered.getattr(attribute)?.try_iter()? { + for item in native_field(discovered, attribute)?.try_iter()? { let item: Bound<'_, PyAny> = item?; let texts: Vec = match (body, blocks) { - (Some(body), _) => vec![item.getattr(body)?.extract()?], + (Some(body), _) => vec![native_field(&item, body)?.extract()?], (None, true) => block_bodies(&native_field(&item, "blocks")?)?, (None, false) => Vec::new(), }; @@ -278,7 +277,7 @@ pub(crate) fn refactor_facts( project: &Bound<'_, PyAny>, request: &RefactorRequest, ) -> PyResult { - let compiled: Bound<'_, PyAny> = native_field(project, "graph")?.getattr("project")?; + let compiled: Bound<'_, PyAny> = native_field(&native_field(project, "graph")?, "project")?; let discovered: Bound<'_, PyAny> = native_field(project, "discovered")?; let dialect: Option = native_field(&compiled, "sql_analysis_dialect")?.extract()?; let mut yaml_files: Vec = @@ -312,10 +311,10 @@ pub(crate) fn declaration_placement( (model, destination): (&str, &str), ) -> PyResult { let py: Python<'_> = project.py(); - let index: Bound<'_, PyAny> = project - .getattr("graph")? - .getattr("project")? - .getattr("scope_index")?; + let index: Bound<'_, PyAny> = native_field( + &native_field(&native_field(project, "graph")?, "project")?, + "scope_index", + )?; let lookup: Bound<'_, PyAny> = build_scope_lookup(py, &index)?.into_bound(py); let (preview, diagnostics) = scope_move_preview( py, @@ -365,9 +364,7 @@ pub(crate) fn declaration_placement( for record in native_field(&index, "declarations")?.try_iter()? { let record: Bound<'_, PyAny> = record?; let identity: Bound<'_, PyAny> = native_field(&record, "identity")?; - let kind: String = native_field(&identity, "kind")? - .getattr("value")? - .extract()?; + let kind: String = native_field(&native_field(&identity, "kind")?, "value")?.extract()?; let name: String = native_field(&identity, "name")?.extract()?; declarations.push(PlacedDeclaration { key: identity_key(&identity)?, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/references/relations.rs b/crates/sqlbuild-python/src/bindings/_helpers/references/relations.rs index 46d397cf98..1c1dcbc8f8 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/references/relations.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/references/relations.rs @@ -78,12 +78,10 @@ pub(crate) fn match_project_relation<'py>( default_schema: Option, ) -> PyResult>> { let name: String = python_casefold(&native_field(relation, "name")?.extract::()?); - let schema: Option = relation - .getattr("schema")? + let schema: Option = native_field(relation, "schema")? .extract::>()? .or(default_schema); - let database: Option = relation - .getattr("database")? + let database: Option = native_field(relation, "database")? .extract::>()? .or(default_database); for candidate in native_field(index, "relations")?.try_iter()? { 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 b0ae22aa68..16158a20b5 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/render/authored_sql.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/render/authored_sql.rs @@ -86,7 +86,7 @@ fn declaration_context<'py>( if declarations.is_none() { PyDict::new(py).into_any() } else { - declarations.getattr(*field)? + native_field(declarations, field)? }, )) }) @@ -296,7 +296,7 @@ fn input_declarations<'py>( return Ok(declarations); } let merged = |field: &str| -> PyResult> { - declarations.getattr(field)?.bitor(local.getattr(field)?) + native_field(&declarations, field)?.bitor(native_field(&local, field)?) }; new_record_with( py, 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 0ee96d3607..081322332f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/render/declaration_indexes.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/render/declaration_indexes.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced_keywords; use crate::bindings::_helpers::records::ids::ErrorId; use crate::bindings::_helpers::records::registry; use pyo3::prelude::{ @@ -11,8 +12,6 @@ use pyo3::prelude::{ use pyo3::types::{PyDict, PyDictMethods, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; -const DATACLASSES_MODULE: &str = "dataclasses"; - /// `CompileInputError(message)`. fn compile_input_error(py: Python<'_>, message: String) -> PyErr { match registry::error(py, ErrorId::CompileInput).and_then(|class| class.call1((message,))) { @@ -22,7 +21,7 @@ fn compile_input_error(py: Python<'_>, message: String) -> PyErr { } fn text(owner: &Bound<'_, PyAny>, name: &str) -> PyResult { - Ok(owner.getattr(name)?.str()?.to_string()) + Ok(native_field(owner, name)?.str()?.to_string()) } /// Add each declaration of `files` to `index`, rejecting a name declared twice. @@ -78,7 +77,6 @@ struct SchemaResolution<'py> { resolved: HashMap>, /// Names in the order Python's `resolved` dict gains them. order: Vec, - replace: Bound<'py, PyAny>, } impl<'py> SchemaResolution<'py> { @@ -122,7 +120,7 @@ impl<'py> SchemaResolution<'py> { } let fields: Bound<'py, PyDict> = PyDict::new(self.py); fields.set_item("columns", PyTuple::new(self.py, columns)?)?; - let resolved: Bound<'py, PyAny> = self.replace.call((declaration,), Some(&fields))?; + let resolved: Bound<'py, PyAny> = replaced_keywords(&declaration, &fields)?; self.resolved.insert(name.to_owned(), resolved.clone()); self.order.push(name.to_owned()); Ok(resolved) @@ -204,7 +202,6 @@ pub(crate) fn public_model_schema_index<'py>( by_name, resolved: HashMap::new(), order: Vec::new(), - replace: py.import(DATACLASSES_MODULE)?.getattr("replace")?, }; let index: Bound<'py, PyDict> = PyDict::new(py); for name in &names { 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 da0bb7688c..368dce5d64 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/render/declaration_values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/render/declaration_values.rs @@ -35,7 +35,7 @@ fn declaration_identity<'py>( owner: Option<&Bound<'py, PyAny>>, ) -> PyResult> { let (identity_class, kinds) = classes; - let kind: Bound<'py, PyAny> = kinds.getattr(kind)?; + let kind: Bound<'py, PyAny> = native_field(kinds, kind)?; match owner { Some(owner) => identity_class.call1((kind, name, owner)), None => identity_class.call1((kind, name)), @@ -54,7 +54,7 @@ fn runtime_declarations<'py>( ); let declarations: Bound<'py, PyDict> = PyDict::new(py); for (files, kind) in [("enum_files", "ENUM"), ("constant_files", "CONSTANT")] { - for file in discovered.getattr(files)?.try_iter()? { + for file in native_field(discovered, files)?.try_iter()? { for declaration in native_field(&file?, "declarations")?.try_iter()? { let declaration: Bound<'py, PyAny> = declaration?; let identity = declaration_identity( @@ -70,13 +70,12 @@ fn runtime_declarations<'py>( let model_kind: Bound<'py, PyAny> = enum_member(py, RecordId::ResourceKind, "MODEL")?; for model_file in native_field(discovered, "model_files")?.try_iter()? { let model_file: Bound<'py, PyAny> = model_file?; - let name: Bound<'py, PyAny> = model_file - .getattr("header_values")? - .call_method1("get", ("name",))?; + let name: Bound<'py, PyAny> = + native_field(&model_file, "header_values")?.call_method1("get", ("name",))?; let name: Bound<'py, PyAny> = if name.is_instance_of::() && name.is_truthy()? { name } else { - native_field(&model_file, "relative_path")?.getattr("stem")? + native_field(&native_field(&model_file, "relative_path")?, "stem")? }; let owner: Bound<'py, PyAny> = new_record(py, RecordId::ResourceIdentity, (&model_kind, name), None)?; @@ -84,7 +83,7 @@ fn runtime_declarations<'py>( ("enum_declarations", "ENUM"), ("constant_declarations", "CONSTANT"), ] { - for declaration in model_file.getattr(attribute)?.try_iter()? { + for declaration in native_field(&model_file, attribute)?.try_iter()? { let declaration: Bound<'py, PyAny> = declaration?; let identity = declaration_identity( &classes, @@ -118,7 +117,8 @@ fn resource_specific<'py>(scope_index: &Bound<'py, PyAny>) -> PyResult> = Vec::new(); for declaration in native_field(scope_index, "declarations")?.try_iter()? { let declaration: Bound<'py, PyAny> = declaration?; - let owner: Bound<'py, PyAny> = native_field(&declaration, "identity")?.getattr("owner")?; + let owner: Bound<'py, PyAny> = + native_field(&native_field(&declaration, "identity")?, "owner")?; if native_field(&declaration, "scope")?.is(&private) && !owner.is_none() { resources.push(owner); } @@ -280,7 +280,7 @@ pub(crate) fn native_declaration_contexts<'py>( lookups.set_item("declarations", &lookup_declarations)?; lookups.set_item( "index_declarations", - native_field(lookup, "index")?.getattr("declarations")?, + native_field(&native_field(lookup, "index")?, "declarations")?, )?; for (name, class) in [ ("resource_record", RecordId::ResourceRecord), @@ -306,8 +306,7 @@ pub(crate) fn native_declaration_contexts<'py>( )?; let global_positions: Bound<'py, PyList> = PyList::new( py, - index - .getattr("global_positions")? + native_field(&index, "global_positions")? .try_iter()? .collect::>>()?, )?; @@ -343,8 +342,7 @@ pub(crate) fn resolved_declaration_context<'py>( let py: Python<'py> = resolver.py(); let resource: Bound<'py, PyAny> = resource.map_or_else(|| py.None().into_bound(py), Clone::clone); - resolver - .getattr("native_contexts")? + native_field(resolver, "native_contexts")? .call_method1("resolve", (resolver, file_path, resource))? .get_item(0) } @@ -369,7 +367,7 @@ fn rendered_constant<'py>( "render_typed_object" }; let rendered: String = - call_with(&value_renderer.getattr(method)?, &[("value", value)])?.extract()?; + call_with(&native_field(value_renderer, method)?, &[("value", value)])?.extract()?; let size: usize = rendered.len(); if size > MAX_SQL_VALUE_SIZE { let context: String = format!( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/render/macro_call_classes.rs b/crates/sqlbuild-python/src/bindings/_helpers/render/macro_call_classes.rs index efd7dddff9..0ae4d7344b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/render/macro_call_classes.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/render/macro_call_classes.rs @@ -99,11 +99,7 @@ impl CallClassState { } let mut injects_context: bool = false; for name in tree_names { - if macros - .get_item(name)? - .getattr("injects_context")? - .is_truthy()? - { + if native_field(¯os.get_item(name)?, "injects_context")?.is_truthy()? { injects_context = true; break; } @@ -166,15 +162,12 @@ impl CallClassState { let declarations: &Bound<'_, PyAny> = inputs.declarations; let visible: bool = !loaded.is_none() && (declarations.is_none() - || !declarations - .getattr("inaccessible_macros")? - .contains(name)?); + || !native_field(declarations, "inaccessible_macros")?.contains(name)?); let token: NameToken = if visible { let identity: Bound<'_, PyAny> = if declarations.is_none() { py.None().into_bound(py) } else { - declarations - .getattr("macro_records")? + native_field(declarations, "macro_records")? .get_item(name)? .getattr("identity")? }; @@ -253,14 +246,12 @@ fn identity_key(identity: &Bound<'_, PyAny>) -> PyResult> { None } else { Some(( - native_field(&owner, "kind")?.getattr("value")?.extract()?, + native_field(&native_field(&owner, "kind")?, "value")?.extract()?, native_field(&owner, "name")?.extract()?, )) }; Ok(Some(( - native_field(identity, "kind")? - .getattr("value")? - .extract()?, + native_field(&native_field(identity, "kind")?, "value")?.extract()?, native_field(identity, "name")?.extract()?, owner_key, ))) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/render/macro_evaluation.rs b/crates/sqlbuild-python/src/bindings/_helpers/render/macro_evaluation.rs index cd0c168c06..8bb2a77dd0 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/render/macro_evaluation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/render/macro_evaluation.rs @@ -128,9 +128,7 @@ fn called_macro<'py>( .invocation_context .call((), Some(&context_arguments))?; expansion.facts.borrow_mut().begin_rendering(approved); - let token: PyResult> = run - .handle - .getattr("render_relation") + let token: PyResult> = native_field(run.handle, "render_relation") .and_then(|renderer| classes.relation_renderer.call_method1("set", (renderer,))); let result: PyResult> = token .as_ref() @@ -291,13 +289,14 @@ fn call_arguments<'py>( } let mut references: Vec = Vec::with_capacity(arguments.typed_references.len()); for (function, reference_name) in &arguments.typed_references { - let kind: String = run - .classes - .ref_kinds - .get_item(function)? - .ok_or_else(|| PyValueError::new_err(function.clone()))? - .getattr("value")? - .extract()?; + let kind: String = native_field( + &run.classes + .ref_kinds + .get_item(function)? + .ok_or_else(|| PyValueError::new_err(function.clone()))?, + "value", + )? + .extract()?; references.push((kind, reference_name.clone())); } run.expansion diff --git a/crates/sqlbuild-python/src/bindings/_helpers/render/macro_expansion.rs b/crates/sqlbuild-python/src/bindings/_helpers/render/macro_expansion.rs index 4c6dae0d0d..c44bc2db8f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/render/macro_expansion.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/render/macro_expansion.rs @@ -377,8 +377,7 @@ impl NativeMacroExpansion { let visibility: Bound<'py, PyAny> = if declarations.is_none() { PyTuple::empty(py).into_any() } else { - declarations - .getattr("macro_visibility")? + native_field(declarations, "macro_visibility")? .call_method1("get", (name, PyTuple::empty(py)))? }; let usages: Vec> = if visibility.is_truthy()? { @@ -547,8 +546,7 @@ impl NativeMacroExpansion { ) -> PyResult> { let declarations: &Bound<'py, PyAny> = self.declarations.bind(py); if !declarations.is_none() { - return declarations - .getattr("macro_records")? + return native_field(declarations, "macro_records")? .get_item(name)? .getattr("identity"); } @@ -677,9 +675,7 @@ fn top_level_output( /// A `SqlResourceRef`'s kind value and name. pub(crate) fn resource_key(reference: &Bound<'_, PyAny>) -> PyResult { Ok(( - native_field(reference, "kind")? - .getattr("value")? - .extract()?, + native_field(&native_field(reference, "kind")?, "value")?.extract()?, native_field(reference, "name")?.extract()?, )) } @@ -706,9 +702,7 @@ fn relationship_consumer( if !consumer.is_instance(&classes.resource_identity)? { return Ok(false); } - let kind: String = native_field(consumer, "kind")? - .getattr("value")? - .extract()?; + let kind: String = native_field(&native_field(consumer, "kind")?, "value")?.extract()?; Ok(RELATIONSHIP_CONSUMER_KINDS.contains(&kind.as_str())) } 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 72d511ab8b..28650810ba 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 @@ -196,10 +196,7 @@ pub(crate) fn macro_output(request: ExpansionRequest<'_>) -> PyResult = Bound::new(py, expansion)?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/render/macro_generated_references.rs b/crates/sqlbuild-python/src/bindings/_helpers/render/macro_generated_references.rs index 47824e589f..87fa73b089 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/render/macro_generated_references.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/render/macro_generated_references.rs @@ -98,9 +98,7 @@ fn resource_kind( return Ok(None); } Ok(Some(( - native_field(consumer, "kind")? - .getattr("value")? - .extract()?, + native_field(&native_field(consumer, "kind")?, "value")?.extract()?, native_field(consumer, "name")?.extract()?, ))) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/result_output/assets.rs b/crates/sqlbuild-python/src/bindings/_helpers/result_output/assets.rs index 106c358225..24ebb20da0 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/result_output/assets.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/result_output/assets.rs @@ -43,7 +43,10 @@ fn targets<'py>( }; let py: Python<'py> = plan.py(); let names: Bound<'py, PyDict> = PyDict::new(py); - for item in plan.getattr(field)?.call_method0("items")?.try_iter()? { + for item in native_field(plan, field)? + .call_method0("items")? + .try_iter()? + { let (name, location): (Bound<'py, PyAny>, Bound<'py, PyAny>) = item?.extract()?; names.set_item(name, native_field(&location, "qualified_name")?)?; } @@ -64,20 +67,12 @@ fn target<'py>( fn last_recorded_sql<'py>(result: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = result.py(); - if !result - .getattr("status")? - .getattr("value")? - .eq(FAILED_STATUS)? - { + if !native_field(&native_field(result, "status")?, "value")?.eq(FAILED_STATUS)? { return Ok(py.None().into_bound(py)); } let events: Vec> = items(&native_field(result, "lifecycle_events")?)?; for event in events.iter().rev() { - if event - .getattr("kind")? - .getattr("value")? - .eq(SQL_EVENT_KIND)? - { + if native_field(&native_field(event, "kind")?, "value")?.eq(SQL_EVENT_KIND)? { return native_field(event, "content"); } } @@ -96,7 +91,7 @@ fn microbatch<'py>(result: &Bound<'py, PyAny>) -> PyResult> { ("strategy", "microbatch_strategy"), ("reason", "microbatch_plan_reason"), ] { - let value: Bound<'py, PyAny> = result.getattr(field)?; + let value: Bound<'py, PyAny> = native_field(result, field)?; if !value.is_none() { state.set_item(key, value)?; } @@ -140,7 +135,7 @@ fn microbatch<'py>(result: &Bound<'py, PyAny>) -> PyResult> { "microbatch_replay_requirement_state", ), ] { - state.set_item(key, result.getattr(field)?)?; + state.set_item(key, native_field(result, field)?)?; } let intervals: Bound<'py, PyList> = PyList::empty(py); for interval in items(&native_field(result, "microbatch_accounting_intervals")?)? { @@ -154,7 +149,7 @@ fn microbatch<'py>(result: &Bound<'py, PyAny>) -> PyResult> { "event_id", ] .into_iter() - .map(|field| Ok((field, interval.getattr(field)?))) + .map(|field| Ok((field, native_field(&interval, field)?))) .collect::>()?; pyo3::types::PyListMethods::append(&intervals, dict(py, fields)?)?; } @@ -173,12 +168,12 @@ fn microbatch<'py>(result: &Bound<'py, PyAny>) -> PyResult> { } fn value<'py>(result: &Bound<'py, PyAny>, field: &str) -> PyResult> { - native_field(&result.getattr(field)?, "value") + native_field(&native_field(result, field)?, "value") } /// `.value` when it is set. fn optional_value<'py>(result: &Bound<'py, PyAny>, field: &str) -> PyResult> { - let value: Bound<'py, PyAny> = result.getattr(field)?; + let value: Bound<'py, PyAny> = native_field(result, field)?; if !value.is_truthy()? { return Ok(value.py().None().into_bound(value.py())); } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/result_output/checks.rs b/crates/sqlbuild-python/src/bindings/_helpers/result_output/checks.rs index b2f50535f9..3a29d5e392 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/result_output/checks.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/result_output/checks.rs @@ -20,11 +20,11 @@ const COLLECTION_KINDS: [&str; 2] = ["list", "set"]; const ERROR_FIELDS: [&str; 3] = ["error_code", "error_help", "error_message"]; fn value<'py>(result: &Bound<'py, PyAny>, field: &str) -> PyResult> { - native_field(&result.getattr(field)?, "value") + native_field(&native_field(result, field)?, "value") } fn optional_value<'py>(result: &Bound<'py, PyAny>, field: &str) -> PyResult> { - let value: Bound<'py, PyAny> = result.getattr(field)?; + let value: Bound<'py, PyAny> = native_field(result, field)?; if value.is_none() { return Ok(value); } @@ -37,7 +37,7 @@ fn fields<'py>( ) -> PyResult)>> { names .iter() - .map(|name| Ok((*name, result.getattr(*name)?))) + .map(|name| Ok((*name, native_field(result, name)?))) .collect() } @@ -48,7 +48,7 @@ fn tuple_of<'py>(py: Python<'py>, entries: Vec>) -> PyResult< /// A SQL value as plain JSON: decimals as text, collections as lists, structs as objects. fn sql_value_json<'py>(value: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = value.py(); - let kind: String = text(&native_field(value, "kind")?.getattr("value")?)?; + let kind: String = text(&native_field(&native_field(value, "kind")?, "value")?)?; let inner: Bound<'py, PyAny> = native_field(value, "value")?; if kind == DECIMAL_KIND { return Ok(inner.str()?.into_any()); @@ -247,7 +247,7 @@ pub(crate) fn measurement_thresholds<'py>( } let rendered: Bound<'py, PyDict> = PyDict::new(thresholds.py()); for severity in ["warn", "error"] { - let bound: Bound<'py, PyAny> = thresholds.getattr(severity)?; + let bound: Bound<'py, PyAny> = native_field(thresholds, severity)?; if !bound.is_none() { rendered.set_item(severity, threshold_bound(&bound)?)?; } 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 e33dbe78eb..420aa0355f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/result_output/documents.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/result_output/documents.rs @@ -130,7 +130,7 @@ fn emitted<'py>( fn count_status(results: &[Bound<'_, PyAny>], field: &str, status: &str) -> PyResult { let mut count: usize = 0; for result in results { - if native_field(&result.getattr(field)?, "value")?.eq(status)? { + if native_field(&native_field(result, field)?, "value")?.eq(status)? { count += 1; } } @@ -140,7 +140,7 @@ fn count_status(results: &[Bound<'_, PyAny>], field: &str, status: &str) -> PyRe fn count_truthy(results: &[Bound<'_, PyAny>], field: &str) -> PyResult { let mut count: usize = 0; for result in results { - if result.getattr(field)?.is_truthy()? { + if native_field(result, field)?.is_truthy()? { count += 1; } } @@ -176,7 +176,7 @@ fn sequence<'py>(py: Python<'py>, value: Option>) -> Bound<'py } fn count(result: &Bound<'_, PyAny>, field: &str) -> PyResult { - result.getattr(field)?.extract() + native_field(result, field)?.extract() } /// Format build command execution results as JSON. @@ -202,7 +202,7 @@ fn audit_projection<'py>( ("failed_count", "audit_result_projection_failed_count"), ] .into_iter() - .map(|(key, field)| Ok((key, result.getattr(field)?))) + .map(|(key, field)| Ok((key, native_field(result, field)?))) .collect::>()?; let failed: bool = counts[2].1.is_truthy()?; let mut entries: Vec<(&'static str, Bound<'py, PyAny>)> = vec![( @@ -284,7 +284,7 @@ fn build_json<'py>( let mut retained: Vec>> = Vec::new(); let mut partial: bool = false; for field in BUILD_RESULT_FIELDS { - let all: Bound<'py, PyAny> = result.getattr(field)?; + let all: Bound<'py, PyAny> = native_field(&result, field)?; let kept: Vec> = projection.terminal_results(&all)?; partial = partial || kept.len() != all.len()?; retained.push(kept); @@ -304,7 +304,7 @@ fn build_json<'py>( for warned in nodes.iter().chain(&checks_run).chain(&retained[3]) { python_warnings += native_field(warned, "warning_messages")?.len()?; } - let result_status: String = text(&native_field(&result, "status")?.getattr("value")?)?; + let result_status: String = text(&native_field(&native_field(&result, "status")?, "value")?)?; let status: String = if result_status == FAILED || python_fail > 0 || check_fail > 0 { FAILED.to_owned() } else { @@ -457,7 +457,7 @@ fn format_clone_execution_json<'py>( if !projection.resource_terminal_recorded(&name, &format!("{resource_type}:{name}"))? { continue; } - let status: Bound<'py, PyAny> = native_field(&item, "status")?.getattr("value")?; + let status: Bound<'py, PyAny> = native_field(&native_field(&item, "status")?, "value")?; failure_count += usize::from(status.eq(FAILED)?); warning_count += usize::from(status.eq("warning")?); assets.push(clone_asset(&projection, &item, &resource_type)?); @@ -591,7 +591,7 @@ fn namespace_execution<'py>( fn scenario_failed(result: &Bound<'_, PyAny>, local: bool) -> PyResult { if !local { - return native_field(result, "status")?.getattr("value")?.eq(FAILED); + return native_field(&native_field(result, "status")?, "value")?.eq(FAILED); } let local_status: Bound<'_, PyAny> = native_field(result, "local_status")?; if local_status.is_none() { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/result_output/integration_records.rs b/crates/sqlbuild-python/src/bindings/_helpers/result_output/integration_records.rs index 718ab80d5d..bda92085b5 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/result_output/integration_records.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/result_output/integration_records.rs @@ -82,12 +82,12 @@ fn entries(values: Vec<(&str, Bound<'_, PyAny>)>) -> Checked { } fn value<'py>(result: &Bound<'py, PyAny>, field: &str) -> PyResult> { - native_field(&result.getattr(field)?, "value") + native_field(&native_field(result, field)?, "value") } /// `.value` when it is truthy, else `None`. fn truthy_value<'py>(result: &Bound<'py, PyAny>, field: &str) -> PyResult> { - let value: Bound<'py, PyAny> = result.getattr(field)?; + let value: Bound<'py, PyAny> = native_field(result, field)?; if !value.is_truthy()? { return Ok(result.py().None().into_bound(result.py())); } @@ -104,7 +104,7 @@ fn planned_target<'py>( if plan.is_none() { return Ok(py.None().into_bound(py)); } - let locations: Bound<'py, PyAny> = plan.getattr(field)?; + let locations: Bound<'py, PyAny> = native_field(plan, field)?; if !locations.contains(name)? { return Ok(py.None().into_bound(py)); } @@ -120,7 +120,7 @@ fn model_microbatch(result: &Bound<'_, PyAny>) -> PyResult { let values: Bound<'_, pyo3::types::PyDict> = pyo3::types::PyDict::new(py); pyo3::types::PyDictMethods::set_item(&values, "run_type", run_type)?; for (key, field) in MICROBATCH_FIELDS { - let item: Bound<'_, PyAny> = result.getattr(field)?; + let item: Bound<'_, PyAny> = native_field(result, field)?; if !item.is_none() { pyo3::types::PyDictMethods::set_item(&values, key, item)?; } @@ -356,7 +356,7 @@ fn terminal_fields(terminal: &Bound<'_, PyAny>, event_sequence: usize) -> PyResu let payload: Bound<'_, PyAny> = native_field(terminal, "payload")?; let get = |key: &str| payload.call_method1("get", (key,)); let attribute = |key: &str| -> PyResult { - Ok(required_json(&terminal.getattr(key)?)?.unwrap_or(JsonValue::Null)) + Ok(required_json(&native_field(terminal, key)?)?.unwrap_or(JsonValue::Null)) }; let mut fields: Entries = vec![ ( @@ -377,7 +377,10 @@ fn terminal_fields(terminal: &Bound<'_, PyAny>, event_sequence: usize) -> PyResu ("invocation_id".to_owned(), attribute("invocation_id")?), ]; for key in ["run_id", "resource_id", "resource_attempt_id"] { - fields.push((key.to_owned(), text_or_empty(&terminal.getattr(key)?)?)); + fields.push(( + key.to_owned(), + text_or_empty(&native_field(terminal, key)?)?, + )); } fields.push(("operation_id".to_owned(), attribute("operation_id")?)); fields.push(("statement_id".to_owned(), attribute("statement_id")?)); 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 80dbb1d709..32dfecc1e6 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/result_output/projection.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/result_output/projection.rs @@ -65,7 +65,7 @@ pub(crate) fn audit_check_id(result: &Bound<'_, PyAny>) -> PyResult { /// The canonical resource name and identifier of an executor result. pub(crate) fn resource_identity(result: &Bound<'_, PyAny>) -> PyResult> { - let name = |field: &str| -> PyResult { text(&result.getattr(field)?) }; + let name = |field: &str| -> PyResult { text(&native_field(result, field)?) }; Ok(Some(match result_kind(result)? { ResultKind::Model => { let resource: String = name("model_name")?; @@ -88,7 +88,7 @@ pub(crate) fn resource_identity(result: &Bound<'_, PyAny>) -> PyResult { let resource: String = name("node_name")?; - let kind: String = text(&native_field(result, "kind")?.getattr("value")?)?; + let kind: String = text(&native_field(&native_field(result, "kind")?, "value")?)?; (resource.clone(), format!("{kind}:{resource}")) } ResultKind::PythonCheck => { @@ -209,11 +209,7 @@ impl<'py> Projection<'py> { else { return Ok(None); }; - number( - &terminal - .getattr("payload")? - .call_method1("get", ("duration_ms",))?, - ) + number(&native_field(&terminal, "payload")?.call_method1("get", ("duration_ms",))?) } /// The rounded canonical duration of a result, else its own `duration_ms`. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/result_output/resource_ids.rs b/crates/sqlbuild-python/src/bindings/_helpers/result_output/resource_ids.rs index 21130eb179..82b98a8bc7 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/result_output/resource_ids.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/result_output/resource_ids.rs @@ -73,7 +73,7 @@ pub(crate) fn sql_test_identity( test_name_field: &str, ) -> PyResult { sql_test_resource_id( - &result.getattr(test_name_field)?.extract::()?, + &native_field(result, test_name_field)?.extract::()?, native_field(result, "source_path")?, native_field(result, "block_index")?.extract()?, native_field(result, "case_name")?.extract()?, 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 2efa5b5e67..fa5273cd2f 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 @@ -70,7 +70,7 @@ fn payload_attempt_number(payload: &Bound<'_, PyAny>) -> PyResult> { } fn optional_text(event: &Bound<'_, PyAny>, name: &str) -> PyResult> { - event.getattr(name)?.extract() + native_field(event, name)?.extract() } /// The facts of one lifecycle event that terminal indexing reads. 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 40d38c9325..d6b9e3722e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_evaluation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_evaluation.rs @@ -21,6 +21,7 @@ use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::compiled_project::records::compiled_project_record; use crate::bindings::_helpers::records::construct::{new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced_keywords; use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::rules::constants::{ CUSTOM_HOST_RUNTIME_VERSION, CUSTOM_RULES_CACHE_FILE, @@ -94,11 +95,8 @@ pub(crate) fn evaluate_custom_rules_cached<'py>( if custom.is_empty() { return outcome(py, (Vec::new(), 0, 0), (0, 0)); } - let enabled: bool = run - .config - .getattr("cache")? - .getattr("enabled")? - .is_truthy()?; + let enabled: bool = + native_field(&native_field(run.config, "cache")?, "enabled")?.is_truthy()?; let mut rows: Vec<(String, Option, Option)> = Vec::new(); for (code, rule) in &custom { let identity: Option = if enabled { @@ -319,7 +317,7 @@ fn host_project<'py>(project: &Bound<'py, PyAny>) -> PyResult> "scope_index", new_record(py, RecordId::ScopeIndex, (), None)?, )?; - imported(py, "dataclasses", "replace")?.call((record,), Some(&changes)) + replaced_keywords(&record, &changes) } fn subject_value(rule: &Bound<'_, PyAny>) -> PyResult> { 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 78a7b839af..da3c4f08cb 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_hosts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/custom_hosts.rs @@ -30,6 +30,7 @@ use crate::bindings::_helpers::compiled_project::records::compiled_project_recor use crate::bindings::_helpers::native_host::constants::CUSTOM_HOST_LAUNCH_MODULE; use crate::bindings::_helpers::records::construct::new_record; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced_keywords; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::constants::{ @@ -418,7 +419,7 @@ pub(crate) fn host_payload_project<'py>( let model: Bound<'py, PyAny> = model?; let fields: Bound<'py, PyDict> = PyDict::new(py); for name in PAYLOAD_MODEL_FIELDS { - fields.set_item(name, model.getattr(name)?)?; + fields.set_item(name, native_field(&model, name)?)?; } models.push(new_record(py, RecordId::CompiledModel, (), Some(&fields))?); } @@ -426,7 +427,7 @@ pub(crate) fn host_payload_project<'py>( let options: Bound<'py, PyDict> = PyDict::new(py); options.set_item("models", PyTuple::new(py, models)?)?; options.set_item("native_project", py.None())?; - imported(py, "dataclasses", "replace")?.call((record,), Some(&options)) + replaced_keywords(&record, &options) } /// Usable CPUs: the process CPU count capped by any cgroup CPU quota. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/findings.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/findings.rs index 201c02078b..bd70da9235 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/findings.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/findings.rs @@ -84,8 +84,7 @@ pub(crate) fn finding_fault(finding: &Bound<'_, PyAny>) -> PyResult { Ok(Fault { unevaluated: native_field(finding, "unevaluated")?.extract()?, code: native_field(finding, "code")?.extract()?, - path: finding - .getattr("path")? + path: native_field(finding, "path")? .call_method0("as_posix")? .extract()?, line: native_field(finding, "line")?.extract()?, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/fixture_formatting.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/fixture_formatting.rs index 5d85cd1d93..154e27d114 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/fixture_formatting.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/fixture_formatting.rs @@ -34,8 +34,7 @@ fn format_redundant_fixture_nulls<'py>( compiler_guard(|| { let py = files.py(); let relations = fixture_schemas::relations(discovered_inputs)?; - let syntax: LexicalSyntax = sql_lexical_syntax - .getattr("native_mapping")? + let syntax: LexicalSyntax = native_field(sql_lexical_syntax, "native_mapping")? .extract::()? .into(); let updated = PyDict::new(py); @@ -67,9 +66,7 @@ fn format_redundant_fixture_nulls<'py>( let block = block?; Ok(FixtureFormatBlock { sql_body: native_field(&block, "sql_body")?.extract()?, - model_mode: block - .getattr("mode")? - .getattr("value")? + model_mode: native_field(&native_field(&block, "mode")?, "value")? .extract::()? == MODEL_TEST_MODE, }) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_inputs.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_inputs.rs index 23a843bc46..0252f0f7e6 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_inputs.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_inputs.rs @@ -83,8 +83,7 @@ pub(crate) fn model_lint_expansion( if record.is_none() { return Ok(None); } - let path: PathBuf = model - .getattr("model_file")? + let path: PathBuf = native_field(model, "model_file")? .getattr(FILE_PATH_ATTRIBUTE)? .extract()?; Ok(Some(( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/relation_keys.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/relation_keys.rs index f673fe6c24..d5f429d621 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/relation_keys.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/relation_keys.rs @@ -54,8 +54,7 @@ pub(crate) fn relation_key_row( } for audit in declared_audits.try_iter()? { let audit: Bound<'_, PyAny> = audit?; - let argument: Option> = audit - .getattr("arguments")? + let argument: Option> = native_field(&audit, "arguments")? .downcast::()? .get_item(UNIQUE_AUDIT_COLUMN_ARGUMENT)?; audits.push(( 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 051bdc54f4..bb591563ad 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_api.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_api.rs @@ -18,6 +18,7 @@ use sqlbuild_rules::project_tree::models::ProjectTreeEntry; use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::records::construct::new_record; +use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -56,7 +57,7 @@ fn position<'py>( return Ok(explicit.clone()); } let py: Python<'py> = node.py(); - let value: Bound<'py, PyAny> = match node.getattr(attribute) { + let value: Bound<'py, PyAny> = match native_field(node, attribute) { Ok(value) => value, Err(error) if error.is_instance_of::(py) => { DEFAULT_POSITION.into_pyobject(py)?.into_any() diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_cache.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_cache.rs index a5515d7d8e..ac9dcc3ecf 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_cache.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/rule_cache.rs @@ -222,8 +222,7 @@ fn lookup_sql_rule_cache<'py>( } fn finding_payload(finding: &Bound<'_, PyAny>) -> PyResult { - let path: String = finding - .getattr("path")? + let path: String = native_field(finding, "path")? .call_method0("as_posix")? .extract()?; Ok(json!({ 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 312da3c8d9..1d5f4dafbd 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/sql_rules.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/sql_rules.rs @@ -268,8 +268,7 @@ fn model_posix_path(model: &Bound<'_, PyAny>) -> PyResult { if let Ok(view) = model.downcast::() { return Ok(view.get().sql_rule_inputs(model.py(), false)?.posix_path); } - model - .getattr("relative_path")? + native_field(model, "relative_path")? .call_method0("as_posix")? .extract() } @@ -277,9 +276,7 @@ fn model_posix_path(model: &Bound<'_, PyAny>) -> PyResult { /// Whether the model's config values set `sql_analysis` to `False`. fn python_excluded(model: &Bound<'_, PyAny>) -> PyResult { let excluded: Bound<'_, PyAny> = false.into_bound_py_any(model.py())?; - Ok(model - .getattr("config")? - .getattr("values")? + Ok(native_field(&native_field(model, "config")?, "values")? .call_method1("get", ("sql_analysis",))? .is(&excluded)) } 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 598976dc7e..2ff8855de1 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 @@ -56,10 +56,8 @@ fn run_scenario_capture_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> P let stream: Bound<'_, PyAny> = py.import("sys")?.getattr("stdout")?; let planning: Bound<'_, PyAny> = write_headers(&stream, &project, (EXECUTION_COMMAND, 1, style))?; - let force: bool = request - .getattr("limit_inputs")? - .getattr("force")? - .is_truthy()?; + let force: bool = + native_field(&native_field(request, "limit_inputs")?, "force")?.is_truthy()?; write(&stream, &format!("{}\n\n", snapshot_capture_warning(force)))?; flush(&stream)?; let pipeline: Bound<'_, PyAny> = compile( @@ -117,10 +115,10 @@ fn run_scenario_capture_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> P ("adapter", adapter.clone()), ( "project_name", - project - .discovered - .getattr("project_config")? - .getattr("name")?, + native_field( + &native_field(&project.discovered, "project_config")?, + "name", + )?, ), ("settings", settings), ("connection_hooks", hooks), 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 16552d5e8a..005ee770c3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/conversions.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/conversions.rs @@ -302,7 +302,7 @@ pub(crate) fn write_warehouse_runtime_target( .iter() .map(|value| { Ok(NamedStatements { - name: value.getattr(field)?.extract()?, + name: native_field(value, field)?.extract()?, statements: sql_statements(value)?, }) }) 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 09853d2f3b..c5e47f3d11 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/setup.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scenario_command/setup.rs @@ -38,6 +38,7 @@ use crate::bindings::_helpers::planner_selection::unit_tests::reject_unit_test_s use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced_keywords; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -96,10 +97,7 @@ pub(crate) struct ScenarioProject<'py> { } fn config_namespace(config: &Bound<'_, PyAny>) -> PyResult> { - config - .getattr("scenario")? - .getattr("run_namespace")? - .extract() + native_field(&native_field(config, "scenario")?, "run_namespace")?.extract() } /// `dataclasses.replace(value, **{name: replacement})`. @@ -109,11 +107,7 @@ fn replaced<'py>( ) -> PyResult> { let changes: Bound<'py, PyDict> = PyDict::new(value.py()); changes.set_item(name, replacement)?; - value - .py() - .import("dataclasses")? - .getattr("replace")? - .call((value,), Some(&changes)) + replaced_keywords(value, &changes) } /// Discover the project, reject unit-test selectors and apply the effective namespace. @@ -323,11 +317,10 @@ pub(crate) fn selected_scenarios<'py>( (selectors, exclude, project_dir): (&[String], &[String], &Bound<'py, PyAny>), ) -> PyResult> { let py: Python<'py> = pipeline.py(); - let compiled: Vec> = pipeline - .getattr("project")? - .getattr("sql_scenarios")? - .try_iter()? - .collect::>()?; + let compiled: Vec> = + native_field(&native_field(pipeline, "project")?, "sql_scenarios")? + .try_iter()? + .collect::>()?; let mut files: Vec = Vec::with_capacity(compiled.len()); for scenario in &compiled { let file: Bound<'py, PyAny> = native_field(scenario, "scenario_file")?; @@ -351,7 +344,7 @@ pub(crate) fn selected_scenarios<'py>( } fn optional_int(value: &Bound<'_, PyAny>, name: &str) -> PyResult> { - value.getattr(name)?.extract() + native_field(value, name)?.extract() } /// The snapshot capture limits from the request, the scenario config and the defaults. 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 a3222fce49..69f78e3f35 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 @@ -85,11 +85,10 @@ pub(crate) fn sync_local_snapshots<'py>( let py: Python<'py> = request.py(); let refresh: bool = native_field(request, "refresh")?.is_truthy()?; let project_connection: Bound<'py, PyAny> = project_connection_config(&run.project)?; - let project_name: Bound<'py, PyAny> = run - .project - .discovered - .getattr("project_config")? - .getattr("name")?; + let project_name: Bound<'py, PyAny> = native_field( + &native_field(&run.project.discovered, "project_config")?, + "name", + )?; let names: Vec = call_with( &host::entry(py, ExecutorHost::SelectScenarioSnapshotCaptureCandidates)?, &[ @@ -142,10 +141,8 @@ pub(crate) fn sync_local_snapshots<'py>( finished?; let scenarios: Bound<'py, PyTuple> = selected_scenarios(&pipeline, (&names, &[], &run.project.project_dir))?; - let force: bool = request - .getattr("limit_inputs")? - .getattr("force")? - .is_truthy()?; + let force: bool = + native_field(&native_field(request, "limit_inputs")?, "force")?.is_truthy()?; let section: SectionStatus<'py> = begin_section( &run.stream, ( 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 7abb03f13f..6dcc1c8692 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 @@ -18,6 +18,7 @@ use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::result_output::documents::format_scenario_execution_json; use crate::bindings::_helpers::result_output::documents::write_execution_json_output; use crate::bindings::_helpers::scenario_command::conversions::succeeded; @@ -52,7 +53,7 @@ pub(crate) struct TestRun<'py> { } fn flag(request: &Bound<'_, PyAny>, name: &str) -> PyResult { - request.getattr(name)?.is_truthy() + native_field(request, name)?.is_truthy() } fn prepare<'py>(py: Python<'py>, request: &Bound<'py, PyAny>) -> PyResult> { @@ -97,9 +98,11 @@ fn prepare<'py>(py: Python<'py>, request: &Bound<'py, PyAny>) -> PyResult = request.getattr("concurrency")?; let concurrency: i64 = if requested.is_none() { - build_effective_settings_config(&project.discovered.clone())? - .getattr("concurrency")? - .extract()? + native_field( + &build_effective_settings_config(&project.discovered.clone())?, + "concurrency", + )? + .extract()? } else { requested.extract()? }; @@ -215,11 +218,10 @@ fn run_scenarios( run.style, ), )?; - let project_name: Bound<'_, PyAny> = run - .project - .discovered - .getattr("project_config")? - .getattr("name")?; + let project_name: Bound<'_, PyAny> = native_field( + &native_field(&run.project.discovered, "project_config")?, + "name", + )?; let mut arguments: Vec<(&str, Bound<'_, PyAny>)> = vec![ ("pipeline_result", pipeline.clone()), ("scenarios", scenarios.clone().into_any()), 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 84afd1aa60..053d61bc1b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/compile_usages.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/compile_usages.rs @@ -98,15 +98,13 @@ pub(crate) fn model_schema_scope_usages<'py>( } let mut records: Vec> = Vec::new(); let mut record_rows: Vec<(IdentityRow, RecordRow)> = Vec::new(); - let declarations = native_field(resolver, "lookup")?.getattr("declarations")?; + let declarations = native_field(&native_field(resolver, "lookup")?, "declarations")?; for item in declarations.call_method0("items")?.try_iter()? { let (identity, found): (Bound<'py, PyAny>, Bound<'py, PyAny>) = item?.extract()?; if !found.is_truthy()? || !native_field(&identity, "owner")?.is_none() || !SCHEMA_USAGE_KINDS.contains( - &identity - .getattr("kind")? - .getattr("value")? + &native_field(&native_field(&identity, "kind")?, "value")? .extract::()? .as_str(), ) @@ -117,9 +115,7 @@ pub(crate) fn model_schema_scope_usages<'py>( let recorded: Bound<'py, PyAny> = native_field(&record, "identity")?; record_rows.push(( ( - native_field(&recorded, "kind")? - .getattr("value")? - .extract()?, + native_field(&native_field(&recorded, "kind")?, "value")?.extract()?, native_field(&recorded, "name")?.extract()?, ), record_row(&record)?, @@ -168,20 +164,17 @@ fn record_row(record: &Bound<'_, PyAny>) -> PyResult { let owner: Bound<'_, PyAny> = native_field(&identity, "owner")?; Ok(( native_field(record, "path")?.extract()?, - native_field(record, "scope")?.getattr("value")?.extract()?, + native_field(&native_field(record, "scope")?, "value")?.extract()?, if owner.is_none() { None } else { Some(( - native_field(&owner, "kind")?.getattr("value")?.extract()?, + native_field(&native_field(&owner, "kind")?, "value")?.extract()?, native_field(&owner, "name")?.extract()?, )) }, native_field(record, "owning_path")?.extract()?, - record - .getattr("ownership_root")? - .getattr("path")? - .extract()?, + native_field(&native_field(record, "ownership_root")?, "path")?.extract()?, )) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/scopes/declaration_resolution.rs b/crates/sqlbuild-python/src/bindings/_helpers/scopes/declaration_resolution.rs index 96947690b0..7ad10099ab 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/declaration_resolution.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/declaration_resolution.rs @@ -11,6 +11,7 @@ use sqlbuild_scopes::scope_index::models::{ DeclarationIdentity, IdentityFailure, QualifiedIdentity, ResourceIdentity, ScopePathFailure, }; +use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::scopes::declaration_contexts::NativeDeclarationContexts; const PATH_CACHE_PREFIX: &str = ""; @@ -276,11 +277,11 @@ impl NativeDeclarationContexts { let py: Python<'py> = context.py(); let fields: Bound<'py, PyDict> = PyDict::new(py); for field in COPIED_FIELDS { - let mapping: Bound<'py, PyDict> = context.getattr(field)?.downcast_into()?; + let mapping: Bound<'py, PyDict> = native_field(context, field)?.downcast_into()?; fields.set_item(field, mapping.copy()?)?; } for field in VISIBILITY_FIELDS { - let mapping: Bound<'py, PyDict> = context.getattr(field)?.downcast_into()?; + let mapping: Bound<'py, PyDict> = native_field(context, field)?.downcast_into()?; fields.set_item(field, self.rebound_visibility(&mapping, consumer)?)?; } fields.set_item(intern!(py, "consumer"), consumer)?; 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 e12cfba468..e8652d8e54 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/relationship_names.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/relationship_names.rs @@ -14,6 +14,7 @@ use sqlbuild_sqltext::sql_scan::models::LexicalSyntax; use crate::bindings::_helpers::boundary::panics::compiler_error; use crate::bindings::_helpers::records::construct::new_record; +use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sqltext::lexical_syntax::LexicalSyntaxInput; @@ -87,8 +88,7 @@ fn extract_sql_test_ctes<'py>( file_label: &str, syntax: &Bound<'py, PyAny>, ) -> PyResult> { - let syntax: LexicalSyntax = syntax - .getattr("native_mapping")? + let syntax: LexicalSyntax = native_field(syntax, "native_mapping")? .extract::()? .into(); let outcome: TopLevelCtes = 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 045c9db4b2..432a8208b3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_command.rs @@ -16,6 +16,7 @@ use sqlbuild_scopes::scope_text::models::ScopeTextRequest; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; use crate::bindings::_helpers::records::construct::{enum_from_value, new_record}; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced_keywords; 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; @@ -191,11 +192,7 @@ fn replaced<'py>( report: &Bound<'py, PyAny>, changes: &Bound<'py, PyDict>, ) -> PyResult> { - report - .py() - .import("dataclasses")? - .getattr("replace")? - .call((report,), Some(changes)) + replaced_keywords(report, changes) } /// The report with extra diagnostics and a part of the result merged in. 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 4798701f02..df43d56320 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_errors.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_errors.rs @@ -22,7 +22,7 @@ pub(crate) fn report_scope_index_errors(index: &Bound<'_, PyAny>) -> PyResult<() { continue; } - let code: Bound<'_, PyAny> = native_field(&item, "code")?.getattr("value")?; + let code: Bound<'_, PyAny> = native_field(&native_field(&item, "code")?, "value")?; let message: Bound<'_, PyAny> = native_field(&item, "message")?; let path: Bound<'_, PyAny> = native_field(&item, "path")?; let declaration: Bound<'_, PyAny> = native_field(&item, "declaration")?; 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 6e8970b78b..372ecbce62 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_index.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_index.rs @@ -123,7 +123,7 @@ fn header_name<'py>(file: &Bound<'py, PyAny>) -> PyResult> { } fn items<'py>(item: &Bound<'py, PyAny>, name: &str) -> PyResult>> { - item.getattr(name)?.try_iter()?.collect() + native_field(item, name)?.try_iter()?.collect() } impl<'py> Walk<'py> { @@ -274,9 +274,7 @@ impl<'py> Walk<'py> { .map(|rendering| native_field(&rendering, "value")) .transpose()?; self.classes.model(RecordId::ConstantMetadata)?.call1(( - declaration - .getattr("logical_type")? - .getattr("display_name")?, + native_field(&native_field(declaration, "logical_type")?, "display_name")?, if is_collection { Some(native_field(&kind, "value")?) } else { @@ -462,7 +460,7 @@ impl<'py> Walk<'py> { [None, None, None], ); let fallback: Bound<'py, PyAny> = or_else(native_field(item, "declaration_root")?, || { - native_field(item, "relative_path")?.getattr("parent") + native_field(&native_field(item, "relative_path")?, "parent") })?; let placement = Self::file_placement(item, text(&fallback)?)?; self.add_declaration(values, placement) @@ -472,7 +470,7 @@ impl<'py> Walk<'py> { let audit_kind: String = DeclarationKind::Audit.as_str().to_owned(); for audit_file in items(inputs, "audit_files")? { let stem_name: Bound<'py, PyAny> = - native_field(&audit_file, "relative_path")?.getattr("stem")?; + native_field(&native_field(&audit_file, "relative_path")?, "stem")?; if value_of(&audit_file, "declaration_kind")? == audit_kind { self.add_named(&audit_file, DeclarationKind::Audit, &stem_name)?; continue; 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 b2f11f9eb8..ab0f6eb8d3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_objects.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_objects.rs @@ -65,26 +65,26 @@ pub(crate) fn enum_text(value: &Bound<'_, PyAny>) -> PyResult { } pub(crate) fn value_of(item: &Bound<'_, PyAny>, name: &str) -> PyResult { - native_field(&item.getattr(name)?, "value")?.extract() + native_field(&native_field(item, name)?, "value")?.extract() } pub(crate) fn field<'py, T: pyo3::FromPyObject<'py>>( item: &Bound<'py, PyAny>, name: &str, ) -> PyResult { - item.getattr(name)?.extract() + native_field(item, name)?.extract() } pub(crate) fn optional_item<'py>( item: &Bound<'py, PyAny>, name: &str, ) -> PyResult>> { - let value: Bound<'py, PyAny> = item.getattr(name)?; + let value: Bound<'py, PyAny> = native_field(item, name)?; Ok(if value.is_none() { None } else { Some(value) }) } fn items<'py>(item: &Bound<'py, PyAny>, name: &str) -> PyResult>> { - item.getattr(name)?.try_iter()?.collect() + native_field(item, name)?.try_iter()?.collect() } /// A resource identity's `(kind, name)` row. @@ -726,5 +726,5 @@ pub(crate) fn tuple_attribute<'py>( item: &Bound<'py, PyAny>, name: &str, ) -> PyResult> { - Ok(item.getattr(name)?.downcast_into::()?) + Ok(native_field(item, name)?.downcast_into::()?) } 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 9c14efc5f1..68014b23f0 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 @@ -26,7 +26,7 @@ type PyObject = Py; const PATHLIB_MODULE: &str = "pathlib"; fn texts(item: &Bound<'_, PyAny>, name: &str) -> PyResult> { - item.getattr(name)? + native_field(item, name)? .try_iter()? .map(|value| value?.str()?.extract()) .collect() @@ -462,7 +462,7 @@ fn read_declaration_reports( item: &Bound<'_, PyAny>, name: &str, ) -> PyResult> { - item.getattr(name)? + native_field(item, name)? .try_iter()? .map(|report| read_declaration_report(&report?)) .collect() @@ -552,8 +552,7 @@ fn read_report(report: &Bound<'_, PyAny>) -> PyResult { directory: field(&resource, "directory")?, duplicate_count: field(&resource, "duplicate_count")?, }, - scope_chain: report - .getattr("scope_chain")? + scope_chain: native_field(report, "scope_chain")? .try_iter()? .map(|entry| { let entry: Bound<'_, PyAny> = entry?; @@ -569,8 +568,7 @@ fn read_report(report: &Bound<'_, PyAny>) -> PyResult { relationship_scope: read_declaration_reports(report, "relationship_scope")?, nearby_unavailable: read_declaration_reports(report, "nearby_unavailable")?, filters: read_filters(&native_field(report, "filters")?)?, - sections: report - .getattr("sections")? + sections: native_field(report, "sections")? .try_iter()? .map(|section| read_section(§ion?)) .collect::>()?, @@ -596,8 +594,7 @@ pub(crate) fn read_result( if result.is_instance(&classes.model(RecordId::ScopeBrowseResult)?)? { return Ok(ScopeResult::Browse(BrowseResult { folder: field(result, "folder")?, - folders: result - .getattr("folders")? + folders: native_field(result, "folders")? .try_iter()? .map(|folder| read_folder(&folder?)) .collect::>()?, 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 db0f001347..46e3030a70 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_reports.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_reports.rs @@ -83,8 +83,7 @@ impl LookupReports { fn lookup_reports(lookup: &Bound<'_, PyAny>) -> PyResult { let index: Bound<'_, PyAny> = native_field(lookup, "index")?; let native: Py = native_field(lookup, "native")?.extract()?; - let rows: Vec = index - .getattr("declarations")? + let rows: Vec = native_field(&index, "declarations")? .try_iter()? .map(|record| Ok(declaration_values(&read_declaration(&record?)?))) .collect::>()?; @@ -166,7 +165,7 @@ pub(crate) fn scope_report( match build_scope_report(reports.source(native), &request) { Ok(report) => { let diagnostics: Bound<'_, PyAny> = - native_field(lookup, "index")?.getattr("diagnostics")?; + native_field(&native_field(lookup, "index")?, "diagnostics")?; Ok(report_object(&classes, report, (&selected, &diagnostics))?.unbind()) } Err(failure) => Err(failure_error(py, &failure)?), @@ -192,7 +191,7 @@ pub(crate) fn scope_explain( match explain_scope_declaration(reports.source(native), &request) { Ok((explanation, diagnostics)) => { let index_diagnostics: Bound<'_, PyAny> = - native_field(lookup, "index")?.getattr("diagnostics")?; + native_field(&native_field(lookup, "index")?, "diagnostics")?; Ok(( explanation_object(&classes, explanation)?.unbind(), diagnostics_object(&classes, &index_diagnostics, raised(diagnostics))?.unbind(), @@ -230,7 +229,8 @@ pub(crate) fn scope_browse( let request: FolderRequest = folder_request(&classes, request, None)?; let native: &NativeScopeLookup = reports.lookup.get(); let result = browse_scope_folders(reports.source(native), &request); - let diagnostics: Bound<'_, PyAny> = native_field(lookup, "index")?.getattr("diagnostics")?; + let diagnostics: Bound<'_, PyAny> = + native_field(&native_field(lookup, "index")?, "diagnostics")?; Ok(browse_object(&classes, result, &diagnostics)?.unbind()) } @@ -249,7 +249,7 @@ pub(crate) fn scope_list( match list_scope_declarations(reports.source(native), &request) { Ok(result) => { let diagnostics: Bound<'_, PyAny> = - native_field(lookup, "index")?.getattr("diagnostics")?; + native_field(&native_field(lookup, "index")?, "diagnostics")?; Ok(list_object(&classes, result, &diagnostics)?.unbind()) } Err(failure) => Err(failure_error(py, &failure)?), @@ -275,7 +275,7 @@ pub(crate) fn scope_move_preview( match preview_scope_move(reports.source(native), &request) { Ok((preview, diagnostics)) => { let index_diagnostics: Bound<'_, PyAny> = - native_field(lookup, "index")?.getattr("diagnostics")?; + native_field(&native_field(lookup, "index")?, "diagnostics")?; Ok(( preview .map(|preview| Ok::<_, PyErr>(move_object(&classes, preview)?.unbind())) 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 36ed52f3c4..4b89e2aeb9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_tolerant.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_tolerant.rs @@ -34,7 +34,7 @@ fn posix(path: &Bound<'_, PyAny>) -> PyResult { } fn items<'py>(item: &Bound<'py, PyAny>, name: &str) -> PyResult>> { - item.getattr(name)?.try_iter()?.collect() + native_field(item, name)?.try_iter()?.collect() } /// New diagnostics with their order keys. 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 880323d8e0..bd9a5be4d8 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_validation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_validation.rs @@ -16,6 +16,7 @@ use sqlbuild_scopes::scope_index::models::ResourceIdentity; use crate::bindings::_helpers::boundary::panics::compiler_error; use crate::bindings::_helpers::records::construct::enum_member; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced_keywords; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::scopes::compile_usages::{ @@ -28,7 +29,6 @@ use crate::bindings::_helpers::scopes::scope_objects::{ }; use crate::bindings::types::CompilerDetach; -const DATACLASSES_MODULE: &str = "dataclasses"; const RUNTIME_USAGE: usize = 2; /// Compile input groups whose items carry declaration usages, after the model inputs. const COMPILE_USAGE_GROUPS: [&str; 5] = [ @@ -49,9 +49,7 @@ pub(crate) fn replace<'py>( for (name, value) in changes { keywords.set_item(*name, value)?; } - py.import(DATACLASSES_MODULE)? - .getattr("replace")? - .call((item,), Some(&keywords)) + replaced_keywords(item, &keywords) } fn tuple_from<'py>(py: Python<'py>, items: Vec>) -> PyResult> { @@ -76,8 +74,7 @@ fn dependency_row( let dependencies: Option> = match optional_item(record, "macro")? { None => None, Some(metadata) => Some( - metadata - .getattr("dependencies")? + native_field(&metadata, "dependencies")? .try_iter()? .map(|item| qualified_row(&item?)) .collect::>()?, @@ -350,9 +347,10 @@ pub(crate) fn scope_index_with_compile_usages<'py>( } else { native_field(&scope, "resolver")? }; - let schema_files: Bound<'py, PyAny> = inputs - .getattr("discovered_inputs")? - .getattr("model_schema_files")?; + let schema_files: Bound<'py, PyAny> = native_field( + &native_field(inputs, "discovered_inputs")?, + "model_schema_files", + )?; let mut usages: Vec> = Vec::new(); usages.extend(usage_records( model_schema_scope_usages(&schema_files, &resolver)?.as_any(), @@ -363,15 +361,15 @@ pub(crate) fn scope_index_with_compile_usages<'py>( usages.extend(usage_records(&native_field(&model, "declaration_usages")?)?); } for group in COMPILE_USAGE_GROUPS { - for item in inputs.getattr(group)?.try_iter()? { + for item in native_field(inputs, group)?.try_iter()? { usages.extend(usage_records(&native_field(&item?, "declaration_usages")?)?); } } - let enforce_placement: bool = inputs - .getattr("project_config")? - .getattr("scopes")? - .getattr("enforce_placement")? - .extract()?; + let enforce_placement: bool = native_field( + &native_field(&native_field(inputs, "project_config")?, "scopes")?, + "enforce_placement", + )? + .extract()?; Ok(scope_placement_validated_index( &native_field(inputs, "scope_index")?, enforce_placement, @@ -398,7 +396,7 @@ pub(crate) fn validate_scope_index(index: &Bound<'_, PyAny>) -> Result<(), PyErr } let mut message: String = String::from("Invalid declaration scope index:"); for item in &errors { - let code: String = native_field(item, "code")?.getattr("value")?.extract()?; + let code: String = native_field(&native_field(item, "code")?, "value")?.extract()?; let text: String = native_field(item, "message")?.extract()?; message.push_str(&format!("\n[{code}] {text}")); } 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 7ed6740113..0d4ce87181 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/seed_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/seed_command/command.rs @@ -30,6 +30,7 @@ use crate::bindings::_helpers::planner_entries::command_plans::build_seed_comman use crate::bindings::_helpers::planner_entries::command_plans::read_selected_seed_fingerprints; use crate::bindings::_helpers::records::construct::{new_record_with, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced_keywords; use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::result_output::documents::format_seed_execution_json; use crate::bindings::_helpers::result_output::documents::write_execution_json_output; @@ -44,7 +45,7 @@ fn text<'py>(py: Python<'py>, value: &str) -> Bound<'py, PyAny> { } fn status_value(result: &Bound<'_, PyAny>) -> PyResult { - native_field(result, "status")?.getattr("value")?.extract() + native_field(&native_field(result, "status")?, "value")?.extract() } /// Forward positional connection timing to a reporter that takes keywords. @@ -140,10 +141,7 @@ impl SeedCompletionRelay { py.import("builtins")?.getattr("int")?.call1((duration,))?; let changes: Bound<'_, PyDict> = PyDict::new(py); changes.set_item("duration_ms", milliseconds)?; - result = py - .import("dataclasses")? - .getattr("replace")? - .call((result,), Some(&changes))?; + result = replaced_keywords(&result, &changes)?; } } let ordinal: usize = *self @@ -289,9 +287,7 @@ fn compile_seeds<'py>( } fn seed_names(pipeline: &Bound<'_, PyAny>) -> PyResult> { - pipeline - .getattr("plan_output")? - .getattr("seed_entries")? + native_field(&native_field(pipeline, "plan_output")?, "seed_entries")? .try_iter()? .map(|entry| native_field(&entry?, "name")?.extract()) .collect() @@ -402,9 +398,10 @@ fn load_seeds<'py>( ("run_id", native_field(&project, "run_id")?), ( "query_change_tracking", - project - .getattr("settings")? - .getattr("query_change_tracking")?, + native_field( + &native_field(&project, "settings")?, + "query_change_tracking", + )?, ), ( "on_seed_complete", @@ -442,11 +439,11 @@ fn run_seed_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult let pipeline: Bound<'_, PyAny> = compile_seeds(request, &invocation)?; let requested: Bound<'_, PyAny> = native_field(request, "concurrency")?; let concurrency: i64 = if requested.is_none() { - pipeline - .getattr("project")? - .getattr("settings")? - .getattr("concurrency")? - .extract::()? + native_field( + &native_field(&native_field(&pipeline, "project")?, "settings")?, + "concurrency", + )? + .extract::()? } else { requested.extract::()? } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/completion.rs b/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/completion.rs index 5408cdde70..d96aedc5b0 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/completion.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/completion.rs @@ -202,7 +202,7 @@ fn completion_model( rejected_opt_out_file: if opt_out.is_none() { None } else { - Some(native_field(&opt_out, "path")?.getattr("name")?.extract()?) + Some(native_field(&native_field(&opt_out, "path")?, "name")?.extract()?) }, }) } 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 c038154c8d..96fb5d1496 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/inputs.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/inputs.rs @@ -46,8 +46,7 @@ pub(crate) fn sql_test_columns<'py>( if !payload.is_instance(model_test_payload)? { continue; } - for cte in payload - .getattr("authored_ctes")? + for cte in native_field(&payload, "authored_ctes")? .try_iter()? .chain(native_field(&payload, "expected_ctes")?.try_iter()?) { @@ -130,7 +129,8 @@ pub(crate) fn resource_sql_diagnostics<'py>( shapes: &Bound<'py, PyAny>, profile: &Bound<'py, PyAny>, ) -> PyResult> { - let native: Bound<'py, PyAny> = native_field(project, "binding_catalog")?.getattr("native")?; + let native: Bound<'py, PyAny> = + native_field(&native_field(project, "binding_catalog")?, "native")?; Ok(resource_sql_diagnostics_of( native.extract::>()?, project, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/metadata.rs b/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/metadata.rs index b62d2db3fb..f3bece1aa5 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/metadata.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/metadata.rs @@ -55,11 +55,7 @@ pub(crate) fn metadata_diagnostics<'py>( catalog: &validation::ProjectCatalog, ) -> PyResult>> { let py: Python<'py> = classes.py(); - if !project - .getattr("settings")? - .getattr("sql_analysis")? - .is_truthy()? - { + if !native_field(&native_field(project, "settings")?, "sql_analysis")?.is_truthy()? { return Ok(Vec::new()); } let (inputs, request) = metadata_request(classes, project, profile)?; @@ -86,7 +82,7 @@ pub(crate) fn metadata_diagnostics<'py>( for (index, finding) in outcome.sources { let source: &Bound<'py, PyAny> = &inputs.sources[index]; let path: Bound<'py, PyAny> = - native_field(source, "source_file")?.getattr("relative_path")?; + native_field(&native_field(source, "source_file")?, "relative_path")?; diagnostics.push(diagnostic( classes, finding, @@ -119,10 +115,7 @@ fn metadata_request<'py>( let mut tests: Vec> = Vec::new(); for test in native_field(project, "sql_tests")?.try_iter()? { let test: Bound<'py, PyAny> = test?; - if test - .getattr("payload")? - .is_instance(&classes.model_test_payload)? - { + if native_field(&test, "payload")?.is_instance(&classes.model_test_payload)? { tests.push(test); } } @@ -139,7 +132,7 @@ fn metadata_request<'py>( let mut metadata_sources: Vec = Vec::with_capacity(sources.len()); for source in &sources { let cursor: Bound<'py, PyAny> = - native_field(source, "source_entry")?.getattr("cursor_column")?; + native_field(&native_field(source, "source_entry")?, "cursor_column")?; let index: usize = file_index(&files, &native_field(source, "source_file")?)?; metadata_sources.push(MetadataSource { name: native_field(source, "name")?.extract()?, @@ -161,9 +154,7 @@ fn metadata_request<'py>( } let request = MetadataRequest { dialect: native_field(profile, "sql_analysis_dialect")?.extract()?, - quoted_ignore_case: profile - .getattr("quoted_identifiers_ignore_case")? - .extract()?, + quoted_ignore_case: native_field(profile, "quoted_identifiers_ignore_case")?.extract()?, return_types: pairs(&native_field(profile, "function_return_types")?)?, functions, shapes: shape_rows(&shapes)?, @@ -248,8 +239,7 @@ fn test_ctes( columns_by_sql: &Bound<'_, PyAny>, ) -> PyResult> { let mut ctes: Vec = Vec::new(); - for cte in payload - .getattr("authored_ctes")? + for cte in native_field(payload, "authored_ctes")? .try_iter()? .chain(native_field(payload, "expected_ctes")?.try_iter()?) { @@ -308,7 +298,7 @@ fn sql_test_diagnostic<'py>( end_column: i64, ) -> PyResult> { let py: Python<'py> = classes.py(); - let path: Bound<'py, PyAny> = native_field(test, "test_file")?.getattr("relative_path")?; + let path: Bound<'py, PyAny> = native_field(&native_field(test, "test_file")?, "relative_path")?; let location: Bound<'py, PyAny> = classes.call( &classes.location, &[ @@ -406,9 +396,7 @@ fn diagnostic<'py>( /// Python's `config.get(sql_analysis) is not False and model.binding_validated`. fn checked(classes: &StageClasses<'_>, model: &Bound<'_, PyAny>) -> PyResult { - let opted: Bound<'_, PyAny> = model - .getattr("config")? - .getattr("values")? + let opted: Bound<'_, PyAny> = native_field(&native_field(model, "config")?, "values")? .call_method1("get", (&classes.sql_analysis_key,))?; if opted.is(PyBool::new(model.py(), false)) { return Ok(false); @@ -424,7 +412,7 @@ fn text(value: &Bound<'_, PyAny>) -> PyResult> { } fn metadata_model(classes: &StageClasses<'_>, model: &Bound<'_, PyAny>) -> PyResult { - let values: Bound<'_, PyAny> = native_field(model, "config")?.getattr("values")?; + let values: Bound<'_, PyAny> = native_field(&native_field(model, "config")?, "values")?; let get = |key: &str| values.call_method1("get", (key,)); let mut references: Vec = Vec::new(); for key in METADATA_CONFIG_KEYS { 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 c600d6f720..f0a57820b7 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 @@ -96,13 +96,12 @@ fn request( } Ok(ResourceSqlRequest { dialect: native_field(profile, "sql_analysis_dialect")?.extract()?, - quoted_ignore_case: profile - .getattr("quoted_identifiers_ignore_case")? - .extract()?, - require_sql_analysis: project - .getattr("settings")? - .getattr("require_sql_analysis")? - .extract()?, + quoted_ignore_case: native_field(profile, "quoted_identifiers_ignore_case")?.extract()?, + require_sql_analysis: native_field( + &native_field(project, "settings")?, + "require_sql_analysis", + )? + .extract()?, shapes: shapes .iter() .map(|(name, columns)| shape(&name, &columns)) @@ -143,9 +142,7 @@ fn audit_input( target_name.extract()?, )) }; - let first_part: Option = file - .getattr("file_path")? - .getattr("parts")? + let first_part: Option = native_field(&native_field(&file, "file_path")?, "parts")? .try_iter()? .next() .transpose()? @@ -154,8 +151,8 @@ fn audit_input( let mut bodies: Vec<(Option, Option)> = Vec::new(); for attribute in ["sql_body", "measure_sql", "evidence_sql"] { bodies.push(( - audit.getattr(attribute)?.extract()?, - block.getattr(attribute)?.extract()?, + native_field(audit, attribute)?.extract()?, + native_field(&block, attribute)?.extract()?, )); } Ok(AuditSqlInput { @@ -180,8 +177,7 @@ fn sql_test_input(test: &Bound<'_, PyAny>) -> PyResult { let block: Bound<'_, PyAny> = native_field(test, "test_block")?; Ok(SqlTestSqlInput { name: native_field(test, "name")?.extract()?, - expected_model_names: test - .getattr("expected_model_names")? + expected_model_names: native_field(test, "expected_model_names")? .try_iter()? .map(|name| name?.extract()) .collect::>()?, @@ -197,7 +193,7 @@ fn model_input( model: &Bound<'_, PyAny>, reference_class: &Bound<'_, PyAny>, ) -> PyResult { - let values: Bound<'_, PyAny> = native_field(model, "config")?.getattr("values")?; + let values: Bound<'_, PyAny> = native_field(&native_field(model, "config")?, "values")?; let mut hooks: Vec = Vec::new(); for key in MODEL_HOOK_KEYS { let entries: Bound<'_, PyAny> = values.call_method1("get", (key,))?; @@ -249,7 +245,7 @@ fn text(value: &Bound<'_, PyAny>) -> PyResult> { /// `getattr(value, name, None)`. fn optional_attribute<'py>(value: &Bound<'py, PyAny>, name: &str) -> PyResult> { if value.hasattr(name)? { - value.getattr(name) + native_field(value, name) } else { Ok(value.py().None().into_bound(value.py())) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/semantic_shapes/coverage.rs b/crates/sqlbuild-python/src/bindings/_helpers/semantic_shapes/coverage.rs index 3e59eaa646..d756c0ffa4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/semantic_shapes/coverage.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/semantic_shapes/coverage.rs @@ -20,16 +20,11 @@ const OPEN_KINDS: [&str; 3] = [SOURCE_KIND, "ref", "dbt_ref"]; pub(crate) fn semantic_coverage<'py>(project: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = project.py(); let reasons: Bound<'py, PyDict> = PyDict::new(py); - if !project - .getattr("settings")? - .getattr("sql_analysis")? - .is_truthy()? - { + if !native_field(&native_field(project, "settings")?, "sql_analysis")?.is_truthy()? { return Ok(reasons); } let shapes: Bound<'py, PyDict> = project_shapes(project, None)?; - let models: Vec> = project - .getattr("models")? + let models: Vec> = native_field(project, "models")? .try_iter()? .collect::>()?; let mut names: Vec = Vec::with_capacity(models.len()); @@ -79,9 +74,7 @@ pub(crate) fn semantic_coverage<'py>(project: &Bound<'py, PyAny>) -> PyResult = model - .getattr("config")? - .getattr("values")? + let opted: Bound<'py, PyAny> = native_field(&native_field(model, "config")?, "values")? .call_method1("get", ("sql_analysis",))?; if opted.is(PyBool::new(py, false)) { reasons.set_item(name, PyTuple::new(py, ["SQL analysis disabled"])?)?; 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 ce6cb0467e..0e45d20bc2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/semantic_shapes/shapes.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/semantic_shapes/shapes.rs @@ -18,9 +18,9 @@ use crate::bindings::_helpers::native_host::adapter::AdapterHost; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::records::construct::enum_member; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced_keywords; use crate::bindings::_helpers::records::ids::RecordId; -const DATACLASSES_MODULE: &str = "dataclasses"; const UNKNOWN: &str = "UNKNOWN"; const GENERIC_DIALECT: &str = "generic"; /// Snapshot validity start column when the model names none. @@ -79,7 +79,7 @@ pub(crate) fn published_shape<'py>( ) -> PyResult> { let py: Python<'py> = model.py(); let shape: Bound<'py, PyDict> = binding_shape(model, columns, inputs, profile)?; - let values: Bound<'py, PyAny> = native_field(model, "config")?.getattr("values")?; + let values: Bound<'py, PyAny> = native_field(&native_field(model, "config")?, "values")?; let snapshot: Bound<'py, PyAny> = enum_member(py, RecordId::MaterializationType, "SNAPSHOT")?; if values .call_method1("get", ("materialized",))? @@ -112,11 +112,7 @@ fn binding_shape<'py>( let dialect: Bound<'py, PyAny> = native_field(profile, "sql_analysis_dialect")?; let sensitive: bool = !dialect.is_none() && CASE_SENSITIVE_BINDING_DIALECTS.contains(&dialect.extract::()?.as_str()); - if profile - .getattr("quoted_identifiers_ignore_case")? - .is_truthy()? - || !sensitive - { + if native_field(profile, "quoted_identifiers_ignore_case")?.is_truthy()? || !sensitive { return Ok(columns.clone()); } let mut catalog: Bound<'py, PyAny> = native_field(profile, "binding_catalog")?; @@ -144,8 +140,7 @@ fn binding_shape<'py>( arguments.set_item("sql", sql)?; arguments.set_item("columns", columns)?; arguments.set_item("inputs", inputs)?; - Ok(catalog - .getattr("inferred_schema")? + Ok(native_field(&catalog, "inferred_schema")? .call((), Some(&arguments))? .downcast_into::()?) } @@ -213,9 +208,7 @@ pub(crate) fn project_profile<'py>( }; let arguments: Bound<'py, PyDict> = PyDict::new(py); arguments.set_item("binding_catalog", native_field(project, "binding_catalog")?)?; - py.import(DATACLASSES_MODULE)? - .getattr("replace")? - .call((profile,), Some(&arguments)) + replaced_keywords(&profile, &arguments) } /// Closed shapes by relation name; absence means open, never an empty physical table. @@ -232,7 +225,7 @@ pub(crate) fn project_shapes<'py>( native_field(&seed, "name")?, declared_columns( py, - &native_field(&seed, "schema_entry")?.getattr("columns")?, + &native_field(&native_field(&seed, "schema_entry")?, "columns")?, )?, )?; } @@ -294,8 +287,7 @@ fn model_shapes<'py>( shapes: &Bound<'py, PyDict>, ) -> PyResult<()> { let py: Python<'py> = project.py(); - let mut pending: Vec> = project - .getattr("models")? + let mut pending: Vec> = native_field(project, "models")? .try_iter()? .collect::>()?; while !pending.is_empty() { @@ -305,10 +297,9 @@ fn model_shapes<'py>( let inferred: Option> = inferred_rows(model)?; let rows: Bound<'py, PyAny> = native_field(model, "reference_rows")?; let relations: Vec = relation_row_names(&rows)?; - let contract: Bound<'py, PyAny> = model - .getattr("config")? - .getattr("values")? - .call_method1("get", ("contract",))?; + let contract: Bound<'py, PyAny> = + native_field(&native_field(model, "config")?, "values")? + .call_method1("get", ("contract",))?; if contract.eq(enforced)? && let Some(declared) = &declared && !dynamic_columns_declared(model)? 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 c9638c5cba..16c3bb82a2 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 @@ -19,6 +19,7 @@ use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::compile_driver::stage_work::count_stage; use crate::bindings::_helpers::records::construct::{enum_from_value, enum_member, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced_keywords; use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::semantic_shapes::constants::SOURCE_REBINDING_ANALYSES; use crate::bindings::_helpers::semantic_shapes::declarations::{declared_types, function_names}; @@ -28,7 +29,6 @@ use crate::bindings::_helpers::semantic_shapes::shapes::{ use crate::bindings::models::BindingPositions; const BINDING_UNKNOWN_TABLE_INTERNAL_CODE: &str = "B000"; -const DATACLASSES_MODULE: &str = "dataclasses"; const GRAPHLIB_MODULE: &str = "graphlib"; const REF_KIND: &str = "ref"; const SOURCE_KIND: &str = "source"; @@ -47,10 +47,7 @@ pub(crate) fn source_binding_diagnostics<'py>( let py: Python<'py> = request.py(); let project: Object<'py> = request.get("project")?; let columns: Object<'py> = request.get("columns")?; - if !project - .getattr("settings")? - .getattr("sql_analysis")? - .is_truthy()? + if !native_field(&native_field(&project, "settings")?, "sql_analysis")?.is_truthy()? || !columns.is_truthy()? { return Ok(PyTuple::empty(py)); @@ -164,9 +161,7 @@ fn rebound_models<'py>( let mut models: Vec> = Vec::new(); for name in order.try_iter()? { let model: Object<'py> = by_name.as_any().get_item(name?)?; - let opted: Object<'py> = model - .getattr("config")? - .getattr("values")? + let opted: Object<'py> = native_field(&native_field(&model, "config")?, "values")? .call_method1("get", ("sql_analysis",))?; if !selected_keys.contains(native_field(&model, "key")?)? || opted.is(PyBool::new(py, false)) @@ -274,9 +269,7 @@ fn validated_diagnostics<'py>( let query_sql: String = analysis_cursor_sql( py, &native_field(model, "query_sql")?.extract::()?, - &model - .getattr("config")? - .getattr("values")? + &native_field(&native_field(model, "config")?, "values")? .call_method1("get", ("cursor_type",))?, )?; requests.push(schema_validation_request( @@ -296,8 +289,7 @@ fn validated_diagnostics<'py>( let results: Object<'py> = schema_validations(py, PyTuple::new(py, &requests)?.as_any())?.into_any(); let expansions: Bound<'py, PyDict> = PyDict::new(py); - for item in project - .getattr("sql_expansions")? + for item in native_field(project, "sql_expansions")? .call_method0("items")? .try_iter()? { @@ -379,7 +371,7 @@ fn positions<'py>( let fields: Vec> = ["source_start", "source_end", "output_start", "output_end"] .into_iter() - .map(|name| span.getattr(name)) + .map(|name| native_field(&span, name)) .collect::>()?; rows.push(PyTuple::new(py, fields)?.into_any()); } @@ -437,9 +429,7 @@ fn adds_physical_evidence( /// `{str(key): str(value)}` of a dict `placeholders` config, else None. fn placeholders<'py>(model: &Object<'py>) -> PyResult> { let py: Python<'py> = model.py(); - let values: Object<'py> = model - .getattr("config")? - .getattr("values")? + let values: Object<'py> = native_field(&native_field(model, "config")?, "values")? .call_method1("get", ("placeholders",))?; if !values.is_instance_of::() { return Ok(py.None().into_bound(py)); @@ -466,9 +456,7 @@ fn replaced<'py>(value: &Object<'py>, pairs: &[(&str, Object<'py>)]) -> PyResult for (name, item) in pairs { arguments.set_item(*name, item)?; } - py.import(DATACLASSES_MODULE)? - .getattr("replace")? - .call((value,), Some(&arguments)) + replaced_keywords(value, &arguments) } fn call<'py>(callable: &Object<'py>, pairs: &[(&str, Object<'py>)]) -> PyResult> { 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 eeeb21fc15..2197014740 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 @@ -41,6 +41,7 @@ use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_entries::command_plans::build_test_command_plan; use crate::bindings::_helpers::records::construct::{new_record_with, record_class}; +use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::result_output::documents::format_test_execution_json; @@ -85,10 +86,9 @@ fn compact_json(payload: &Bound<'_, PyAny>) -> PyResult { /// The label of one test or result: its name, plus source and values for parameterized cases. pub(crate) fn test_label(item: &Bound<'_, PyAny>, name_field: &str) -> PyResult { - let name: String = item.getattr(name_field)?.extract()?; + let name: String = native_field(item, name_field)?.extract()?; let source_path: Bound<'_, PyAny> = item.getattr("source_path")?; - let values: Vec> = item - .getattr("parameter_values")? + let values: Vec> = native_field(item, "parameter_values")? .try_iter()? .collect::>()?; if source_path.is_none() || values.is_empty() { @@ -114,9 +114,7 @@ pub(crate) fn test_label(item: &Bound<'_, PyAny>, name_field: &str) -> PyResult< }; parameters.push(TestParameter { name: value_name, - value_type: declaration - .getattr("value_type")? - .getattr("value")? + value_type: native_field(&native_field(declaration, "value_type")?, "value")? .extract()?, nullable: declaration.getattr("nullable")?.is_truthy()?, rendered: compact_json(&payload)?, @@ -360,9 +358,10 @@ fn compile_tests<'py>( ("options", options), ( "on_progress", - invocation - .getattr("planning_progress")? - .getattr("on_progress")?, + native_field( + &native_field(invocation, "planning_progress")?, + "on_progress", + )?, ), ], )?))?; @@ -442,13 +441,11 @@ fn inspection_entry(entry: &Bound<'_, PyAny>) -> PyResult { /// Write the resolved plan without executing it; returns the exit code. fn inspect(stream: &Bound<'_, PyAny>, plan_output: &Bound<'_, PyAny>) -> PyResult { - let entries: Vec = plan_output - .getattr("test_entries")? + let entries: Vec = native_field(plan_output, "test_entries")? .try_iter()? .map(|entry| inspection_entry(&entry?)) .collect::>()?; - let warnings: Vec = plan_output - .getattr("warnings")? + let warnings: Vec = native_field(plan_output, "warnings")? .try_iter()? .map(|warning| { let warning: Bound<'_, PyAny> = warning?; @@ -601,10 +598,7 @@ fn complete( let py: Python<'_> = request.py(); let mut failures: Vec = Vec::new(); for result in results { - if result - .getattr("outcome")? - .getattr("value")? - .extract::()? + if native_field(&native_field(result, "outcome")?, "value")?.extract::()? == PASS_OUTCOME { continue; @@ -659,11 +653,11 @@ fn run_test_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult } let requested: Bound<'_, PyAny> = request.getattr("concurrency")?; let concurrency: i64 = if requested.is_none() { - pipeline - .getattr("project")? - .getattr("settings")? - .getattr("concurrency")? - .extract()? + native_field( + &native_field(&native_field(&pipeline, "project")?, "settings")?, + "concurrency", + )? + .extract()? } else { requested.extract()? }; @@ -678,8 +672,7 @@ fn run_test_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult let results: Vec> = run_tests(&invocation, &pipeline, concurrency)?; write_test_runtime_target( ( - &invocation - .getattr("effective_project_dir")? + &native_field(&invocation, "effective_project_dir")? .call_method1("__truediv__", ("target",))?, &invocation.getattr("adapter")?, ), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/assembly.rs b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/assembly.rs index 6948cafe2f..55b7a7de1f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/assembly.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/assembly.rs @@ -77,15 +77,14 @@ fn assembly_model(model: &Bound<'_, PyAny>) -> PyResult { None }; Ok(SqlTestAssemblyModel { - name: model - .getattr("model_file")? - .getattr("file_path")? - .getattr("stem")? - .extract()?, + name: native_field( + &native_field(&native_field(model, "model_file")?, "file_path")?, + "stem", + )? + .extract()?, macro_deps, unscanned_macro_source, - references: model - .getattr("references")? + references: native_field(model, "references")? .try_iter()? .map(|reference| { let reference = reference?; @@ -115,20 +114,16 @@ fn assembly_test(test: &Bound<'_, PyAny>) -> PyResult { expected_ctes: ctes(&payload, "expected_ctes")?, assertion_ctes: ctes(&payload, "assertion_ctes")?, expected_model_names: native_field(&payload, "expected_model_names")?.extract()?, - assertion_target_model_names: payload - .getattr("assertion_target_model_names")? + assertion_target_model_names: native_field(&payload, "assertion_target_model_names")? .extract()?, - reference_target_model_names: payload - .getattr("reference_target_model_names")? + reference_target_model_names: native_field(&payload, "reference_target_model_names")? .extract()?, mock_model_names: native_field(&payload, "mock_model_names")?.extract()?, }) }; Ok(SqlTestAssemblyTest { block_name: native_field(&test_block, "name")?.extract()?, - file_stem: native_field(&test_file, "file_path")? - .getattr("stem")? - .extract()?, + file_stem: native_field(&native_field(&test_file, "file_path")?, "stem")?.extract()?, relative_path: relative_path.call_method0("as_posix")?.extract()?, relative_stem: native_field(&relative_path, "stem")?.extract()?, contents: native_field(&test_file, "contents")?.extract()?, @@ -136,8 +131,7 @@ fn assembly_test(test: &Bound<'_, PyAny>) -> PyResult { block_index: native_field(&test_block, "test_index")?.extract()?, sql_body: native_field(test, "sql_body")?.extract()?, case_name: native_field(test, "case_name")?.extract()?, - parameter_schema: test - .getattr("parameter_schema")? + parameter_schema: native_field(test, "parameter_schema")? .try_iter()? .map(|parameter| { let parameter = parameter?; @@ -148,8 +142,7 @@ fn assembly_test(test: &Bound<'_, PyAny>) -> PyResult { )) }) .collect::>()?, - parameter_values: test - .getattr("parameter_values")? + parameter_values: native_field(test, "parameter_values")? .try_iter()? .map(|entry| { let (name, value): (String, Bound<'_, PyAny>) = entry?.extract()?; @@ -176,9 +169,7 @@ fn ctes(owner: &Bound<'_, PyAny>, attribute: &str) -> PyResult> /// One `SqlValue`, read by its logical kind; decimals keep `as_tuple()`'s digits. fn parameter_value(value: &Bound<'_, PyAny>) -> PyResult { - let kind: String = native_field(value, "logical_type")? - .getattr("kind")? - .extract()?; + let kind: String = native_field(&native_field(value, "logical_type")?, "kind")?.extract()?; let payload = native_field(value, "value")?; Ok(match kind.as_str() { "string" => SqlTestParameterValue::String(payload.extract()?), 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 bd9a742f8a..3be1559613 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 @@ -54,8 +54,7 @@ impl AuthoredBody { Ok(Self { sql: sql.extract()?, contents: native_field(file, "contents")?.extract()?, - relative_path: file - .getattr("relative_path")? + relative_path: native_field(file, "relative_path")? .call_method0("as_posix")? .extract()?, file_label: native_field(file, "relative_path")?.str()?.extract()?, 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 b5e8dd09c8..029f6f42a9 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 @@ -112,7 +112,7 @@ fn copied<'py>( ) -> PyResult)>> { fields .iter() - .map(|field| Ok((*field, source.getattr(*field)?))) + .map(|field| Ok((*field, native_field(source, field)?))) .collect() } @@ -163,7 +163,7 @@ fn keyed_diagnostic<'py>( ) -> PyResult> { let py: Python<'py> = classes.py(); let path: Bound<'py, PyAny> = - native_field(test_input, "test_file")?.getattr("relative_path")?; + native_field(&native_field(test_input, "test_file")?, "relative_path")?; let key: Bound<'py, PyTuple> = PyTuple::new( py, [ @@ -299,10 +299,11 @@ pub(crate) fn assemble_native_sql_tests<'py>( let request = AssemblyRequest { model_inputs: native_field(inputs, "model_inputs")?.extract()?, test_inputs: test_inputs.clone(), - lexical_syntax: inputs - .getattr("sql_lexical_syntax")? - .getattr("native_mapping")? - .extract()?, + lexical_syntax: native_field( + &native_field(inputs, "sql_lexical_syntax")?, + "native_mapping", + )? + .extract()?, }; let rows: Vec = assemble_compiled_sql_tests(py, request)?; if rows.len() != test_inputs.len() { 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 0ed8457f9b..6d122f641c 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 @@ -120,7 +120,7 @@ fn test_window<'py>( let Some(block) = &block else { return Ok(None); }; - let value = block.getattr(name)?; + let value = native_field(block, name)?; Ok((!value.is_none()).then_some(value)) }; let integer = cursor.type_name == INTEGER_CURSOR_TYPE; @@ -238,7 +238,7 @@ fn ordering(left: &Bound<'_, PyAny>, right: &Bound<'_, PyAny>) -> PyResult(model: &Bound<'py, PyAny>) -> PyResult> { let py = model.py(); - let values = native_field(model, "config")?.getattr("values")?; + let values = native_field(&native_field(model, "config")?, "values")?; let declared = config_str(&values, "cursor_type")?.filter(|value| { value .extract::() @@ -272,8 +272,6 @@ fn test_label(test: &Bound<'_, PyAny>) -> PyResult { Ok(format!( "SQL test '{}' in '{}'", native_field(test, "name")?.str()?, - native_field(test, "test_file")? - .getattr("relative_path")? - .str()? + native_field(&native_field(test, "test_file")?, "relative_path")?.str()? )) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/fixture_validation.rs b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/fixture_validation.rs index 3fbc5e1b68..4f71a89e3b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/fixture_validation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/fixture_validation.rs @@ -198,8 +198,7 @@ pub(super) fn fixture_location( .map_err(value_error)?; Ok(format!( "{}:{line}", - test_file - .getattr("relative_path")? + native_field(&test_file, "relative_path")? .call_method0("as_posix")? .extract::()? )) 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 ed5d036a6f..a9f5107290 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 @@ -27,10 +27,11 @@ pub(super) fn chains<'py>( let request = ChainRequest { models: native_field(project, "models")?.extract()?, tests: tests.extract()?, - lexical_syntax: project - .getattr("sql_lexical_syntax")? - .getattr("native_mapping")? - .extract()?, + lexical_syntax: native_field( + &native_field(project, "sql_lexical_syntax")?, + "native_mapping", + )? + .extract()?, }; let native = resolve_compiled_sql_test_chains(py, request).and_then(|chains| object(py, chains)); @@ -88,10 +89,11 @@ pub(crate) fn planned<'py>( requires_derived_table_aliases: adapter .call_method0("requires_derived_table_aliases")? .extract()?, - lexical_syntax: project - .getattr("sql_lexical_syntax")? - .getattr("native_mapping")? - .extract()?, + lexical_syntax: native_field( + &native_field(project, "sql_lexical_syntax")?, + "native_mapping", + )? + .extract()?, render_sql: artifact || options.render_sql, include_plan: !artifact && options.include_plan, }; @@ -215,10 +217,7 @@ pub(crate) fn sql_test_plan_error_messages<'py>( for warning in warnings.try_iter()? { let warning = warning?; let message: String = native_field(&warning, "message")?.extract()?; - if warning - .getattr("severity")? - .getattr("value")? - .extract::()? + if native_field(&native_field(&warning, "severity")?, "value")?.extract::()? == super::constants::ERROR_SEVERITY && seen.insert(message.clone()) { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_entries.rs b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_entries.rs index 26fd60e004..7bdb0110f8 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_entries.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/plan_entries.rs @@ -18,7 +18,7 @@ fn prefixed_names<'py>(test: &Bound<'py, PyAny>, prefix: &str) -> PyResult = if model_payload(test)? { prefixed_ctes( - &native_field(test, "payload")?.getattr("authored_ctes")?, + &native_field(&native_field(test, "payload")?, "authored_ctes")?, prefix, )? .keys() @@ -49,9 +49,8 @@ fn function_deps<'py>( }; for dep in native_field(&model, "deps")?.try_iter()? { let dep = dep?; - let resource: String = native_field(&dep, "resource_type")? - .getattr("value")? - .extract()?; + let resource: String = + native_field(&native_field(&dep, "resource_type")?, "value")?.extract()?; if resource != UDF && resource != TABLE_FN { continue; } @@ -65,21 +64,17 @@ fn function_deps<'py>( } } else { let payload = native_field(test, "payload")?; - let mode: String = native_field(&payload, "mode")? - .getattr("value")? - .extract()?; + let mode: String = native_field(&native_field(&payload, "mode")?, "value")?.extract()?; if mode == UDF || mode == TABLE_FN { - let names: std::collections::HashSet = payload - .getattr("tested_resource_names")? - .extract::>()? - .into_iter() - .collect(); + let names: std::collections::HashSet = + native_field(&payload, "tested_resource_names")? + .extract::>()? + .into_iter() + .collect(); for function in native_field(project, "functions")?.try_iter()? { let function = function?; let key = native_field(&function, "key")?; - if key - .getattr("resource_type")? - .getattr("value")? + if native_field(&native_field(&key, "resource_type")?, "value")? .extract::()? == mode && names.contains(&native_field(&function, "name")?.extract::()?) @@ -113,7 +108,7 @@ pub(super) fn entry<'py>( "parameter_values", "scope_deps", ] { - fields.set_item(name, test.getattr(name)?)?; + fields.set_item(name, native_field(test, name)?)?; } for (field, prefix) in [ ("mock_ref_names", "__ref__"), 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 e7bf07b7db..3a823683c2 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 @@ -21,10 +21,7 @@ fn cte_call_location<'py>( let call: Option = call.extract()?; let located: CteLocation = sql_test_cte_location( &native_field(&test_file, "contents")?.extract::()?, - &test - .getattr("test_block")? - .getattr("sql_body")? - .extract::()?, + &native_field(&native_field(test, "test_block")?, "sql_body")?.extract::()?, &cte.extract::()?, call.as_deref(), ); @@ -49,9 +46,7 @@ pub(super) fn planning_error( for test in tests.try_iter()? { let test = test?; if native_field(&test, "name")?.eq(payload.get_item("testName")?)? - && test - .getattr("test_file")? - .getattr("relative_path")? + && native_field(&native_field(&test, "test_file")?, "relative_path")? .str()? .into_any() .eq(payload.get_item("fileLabel")?)? 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 39a7915d13..8506fa10b6 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 @@ -3,9 +3,9 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; -use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::planner_strategy::models::RelationFixturePlanningContext; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced_keywords; use crate::bindings::_helpers::sql_test_glue::authored_ctes::prefixed_ctes; use crate::bindings::_helpers::sql_test_glue::fixture_validation::validated_fixtures; use crate::bindings::_helpers::sql_test_glue::plan_driver::resolve_sql_test_model_chains; @@ -38,7 +38,6 @@ pub(super) fn validated_test<'py>( Ok(completed) => completed, Err(diagnostics) => return Ok(Err(diagnostics)), }; - let replace = imported(py, "dataclasses", "replace")?; let mut authored: Vec> = Vec::new(); for cte in ctes.try_iter()? { let cte = cte?; @@ -50,7 +49,7 @@ pub(super) fn validated_test<'py>( .call_method1("get", (name, native_field(&cte, "sql_body")?))?; let kwargs = PyDict::new(py); kwargs.set_item("sql_body", sql)?; - replacement = replace.call((&cte,), Some(&kwargs))?; + replacement = replaced_keywords(&cte, &kwargs)?; break; } } @@ -66,15 +65,15 @@ pub(super) fn validated_test<'py>( "sql_body", completed[3].call_method1("get", (name, native_field(&cte, "sql_body")?))?, )?; - expected_ctes.push(replace.call((&cte,), Some(&kwargs))?); + expected_ctes.push(replaced_keywords(&cte, &kwargs)?); } let kwargs = PyDict::new(py); kwargs.set_item("authored_ctes", PyTuple::new(py, authored)?)?; kwargs.set_item("expected_ctes", PyTuple::new(py, expected_ctes)?)?; - let payload = replace.call((payload,), Some(&kwargs))?; + let payload = replaced_keywords(&payload, &kwargs)?; let kwargs = PyDict::new(py); kwargs.set_item("payload", payload)?; - replace.call((test,), Some(&kwargs)).map(Ok) + replaced_keywords(test, &kwargs).map(Ok) } pub(super) fn validate<'py>( 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 2d264c9324..33632f4654 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 @@ -76,9 +76,7 @@ pub(super) fn overrides<'py>( if !model_payload(test)? { continue; } - let overrides = test - .getattr("payload")? - .getattr("model_query_overrides")? + let overrides = native_field(&native_field(test, "payload")?, "model_query_overrides")? .cast_into::()?; let chain = chains.get(&index); if chain.is_none() @@ -145,7 +143,7 @@ pub(super) fn functions<'py>( let mut result: Vec> = Vec::new(); for function in native_field(project, "functions")?.try_iter()? { let function = function?; - let target = native_field(&function, "destination")?.getattr("qualified_name")?; + let target = native_field(&native_field(&function, "destination")?, "qualified_name")?; if target.is_none() { continue; } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/planning.rs b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/planning.rs index 7dd187e01d..82d8ff5e76 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/planning.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/sql_test_glue/planning.rs @@ -161,10 +161,7 @@ fn model_dependencies(model: &Bound<'_, PyAny>) -> PyResult> { let mut names: Vec = Vec::new(); for dependency in native_field(model, "deps")?.try_iter()? { let dependency = dependency?; - if dependency - .getattr("resource_type")? - .eq(MODEL_RESOURCE_TYPE)? - { + if native_field(&dependency, "resource_type")?.eq(MODEL_RESOURCE_TYPE)? { names.push(native_field(&dependency, "name")?.extract()?); } } @@ -219,9 +216,7 @@ fn plan_test( }; Ok(SqlTestPlanTest { name: native_field(test, "name")?.extract()?, - file_label: test - .getattr("test_file")? - .getattr("relative_path")? + file_label: native_field(&native_field(test, "test_file")?, "relative_path")? .str()? .extract()?, payload, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/state_rows/migration_io.rs b/crates/sqlbuild-python/src/bindings/_helpers/state_rows/migration_io.rs index 1efd78978c..681fb831cd 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/state_rows/migration_io.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/state_rows/migration_io.rs @@ -116,7 +116,7 @@ fn subject(table: EventTable, event: &Bound<'_, PyAny>) -> PyResult { EventTable::OldName => format!( "{} old name {}", text(native_field(event, "destination_model")?)?, - text(native_field(event, "old")?.getattr("name")?)? + text(native_field(&native_field(event, "old")?, "name")?)? ), }) } @@ -268,9 +268,7 @@ fn read_renames<'py>(request: Option<&Bound<'py, PyDict>>) -> PyResult( } fn step(fact: &Bound<'_, PyAny>) -> PyResult { - let value: String = native_field(fact, "event_type")? - .getattr("value")? - .extract()?; + let value: String = native_field(&native_field(fact, "event_type")?, "value")?.extract()?; match value.as_str() { "required" => Ok(OldNameStep::Required), "origin_archived" => Ok(OldNameStep::OriginArchived), 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 0805e5d79b..1074c0797e 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 @@ -88,7 +88,7 @@ fn optional<'py>(value: Bound<'py, PyAny>, attribute: &str) -> PyResult( event: &Bound<'py, PyAny>, ) -> PyResult>> { let py: Python<'py> = event.py(); - let field = |name: &str| event.getattr(name); + let field = |name: &str| native_field(event, name); let relation = |name: &str| -> PyResult<[Bound<'py, PyAny>; 3]> { - let relation: Bound<'py, PyAny> = event.getattr(name)?; + let relation: Bound<'py, PyAny> = native_field(event, name)?; Ok([ native_field(&relation, "database")?, native_field(&relation, "schema")?, @@ -129,7 +129,7 @@ fn row_values<'py>( values.push(field("destination_column")?); } EventTable::OldName => { - values.push(field("event_type")?.getattr("value")?); + values.push(native_field(&field("event_type")?, "value")?); values.push(field("migration_event_id")?); values.push(field("destination_model")?); values.extend(relation("old")?); @@ -152,8 +152,8 @@ fn row_values<'py>( return Ok(values); } } - values.push(field("discovery")?.getattr("value")?); - values.push(field("decision")?.getattr("value")?); + values.push(native_field(&field("discovery")?, "value")?); + values.push(native_field(&field("decision")?, "value")?); values.push(field("run_id")?); values.push(field("created_at")?); Ok(values) 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 90a55d550b..eb11cae7ff 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 @@ -19,6 +19,7 @@ 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::native_host::adapter::AdapterHost; use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::fields::field as native_field; 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. @@ -43,7 +44,7 @@ pub(crate) fn state_table_create_sql<'py>( let mut rendered: Vec<(&str, String)> = Vec::with_capacity(DECLARED_TYPES.len()); for (declared, framework) in DECLARED_TYPES { let text: String = render_framework_type - .call1((framework_types.getattr(framework)?,))? + .call1((native_field(&framework_types, framework)?,))? .extract()?; rendered.push((declared, text)); } @@ -123,7 +124,7 @@ fn render_insert_source_freshness_records_sql<'py>( let row: Vec = SOURCE_FRESHNESS_COLUMN_TYPES .iter() .map(|(column, declared)| { - render_state_sql_literal(py, &record.getattr(*column)?, declared) + render_state_sql_literal(py, &native_field(&record, column)?, declared) }) .collect::>()?; rows.push(row); From 26014ef51e8a741199711a85d9d1546c9d407578 Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Sat, 17 Oct 2026 03:52:18 +0100 Subject: [PATCH 11/28] refactor: build planner records and the default scope index natively Planner record helpers construct through records::construct, the scope command loads its default index with the Rust loader, and the now-unused NativeItem / registry::item lookup is removed. --- .../_helpers/planner_entries/values.rs | 10 +++---- .../planner_migrations/column_objects.rs | 8 ++--- .../_helpers/planner_phases/objects.rs | 8 ++--- .../src/bindings/_helpers/records/ids.rs | 26 ---------------- .../src/bindings/_helpers/records/registry.rs | 11 +------ .../bindings/_helpers/scopes/scope_command.rs | 30 +++++++++++-------- .../bindings/_helpers/scopes/scope_loading.rs | 2 +- 7 files changed, 32 insertions(+), 63 deletions(-) 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 78f9af0c81..f8b62f1a38 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/values.rs @@ -1,9 +1,9 @@ //! Python boundary values shared by plan entry and plan output assembly. -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, keywords}; +use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::records::construct::{enum_member, new_record_with}; use crate::bindings::_helpers::records::fields::replaced_keywords; -use crate::bindings::_helpers::records::ids::{ErrorId, NativeItem, RecordId}; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; use pyo3::IntoPyObject; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python}; @@ -12,13 +12,13 @@ use pyo3::types::PyString; pub(super) const MICROBATCH_CONCURRENCY_MESSAGE: &str = "batch_concurrency > 1 requires concurrent microbatches; selected model(s): "; -/// `sqlbuild._native.name(**pairs)`. +/// The native record `name(**pairs)`. pub(super) fn native<'py>( py: Python<'py>, - name: impl Into, + name: RecordId, pairs: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - call_with(®istry::item(py, name)?, pairs) + new_record_with(py, name, pairs) } /// The planner record `name(**pairs)`. 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 a3ed28aa03..78a498dc4f 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,15 +1,13 @@ //! Boundary conversions for the column migration planner. use crate::bindings::_helpers::analysis::model_reads; -use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::model_config::config_values::config_str as authored_config_str; use crate::bindings::_helpers::planner_identity::hashing::model_definition_hash; use crate::bindings::_helpers::planner_phases::objects::object; use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::fields::replaced_keywords; -use crate::bindings::_helpers::records::ids::{NativeItem, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::ids::RecordId; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyString}; @@ -42,10 +40,10 @@ pub(super) fn model<'py>( pub(super) fn native<'py>( py: Python<'py>, - name: impl Into, + name: RecordId, fields: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - call_with(®istry::item(py, name)?, fields) + new_record_with(py, name, fields) } pub(super) fn config<'py>(model: &Bound<'py, PyAny>, key: &str) -> PyResult> { 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 59bc2c2d02..c66284f8e2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/objects.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_phases/objects.rs @@ -7,7 +7,7 @@ use crate::bindings::_helpers::native_host::models::HostId; use crate::bindings::_helpers::records::construct::{enum_member, new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::fields::replaced_keywords; -use crate::bindings::_helpers::records::ids::{ErrorId, NativeItem, RecordId}; +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::{PyFrozenSet, PyString, PyTuple}; @@ -41,13 +41,13 @@ pub(crate) fn planner_model<'py>( new_record_with(py, name, fields) } -/// `sqlbuild._native.name(**fields)`. +/// The native record `name(**fields)`. pub(crate) fn native<'py>( py: Python<'py>, - name: impl Into, + name: RecordId, fields: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - call_with(®istry::item(py, name)?, fields) + new_record_with(py, name, fields) } /// `dataclasses.replace(value, **fields)`. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/records/ids.rs b/crates/sqlbuild-python/src/bindings/_helpers/records/ids.rs index f6a44b1f4d..208a4bcf4d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/records/ids.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/records/ids.rs @@ -1018,14 +1018,6 @@ native_ids! { } } -/// 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 { @@ -1039,21 +1031,3 @@ impl NativeAttr { 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/registry.rs b/crates/sqlbuild-python/src/bindings/_helpers/records/registry.rs index abc0274b79..52b87f24cb 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/records/registry.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/records/registry.rs @@ -9,7 +9,7 @@ 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}; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; /// The extension module, imported once for attribute lookups. static MODULE: PyOnceLock> = PyOnceLock::new(); @@ -137,15 +137,6 @@ pub(crate) fn member<'py>( 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(|| { 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 432a8208b3..ca7a6aa86d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_command.rs @@ -17,8 +17,9 @@ use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_colo use crate::bindings::_helpers::records::construct::{enum_from_value, new_record}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::fields::replaced_keywords; -use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::scopes::scope_loading::load_or_build_scope_index; 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; @@ -340,18 +341,23 @@ fn run_scope_command( 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 => registry::native(py, NativeAttr::LoadOrBuildScopeIndex)?, + let index: Bound<'_, PyAny> = match load_scope_index { + Some(loader) => { + let options: Bound<'_, PyDict> = keywords( + py, + &[ + ("project_dir", project_dir), + ("no_cache", native_field(request, "no_cache")?), + ], + )?; + loader.call((), Some(&options))? + } + None => load_or_build_scope_index( + &project_dir, + native_field(request, "no_cache")?.is_truthy()?, + )? + .into_any(), }; - let options: Bound<'_, PyDict> = keywords( - py, - &[ - ("project_dir", project_dir), - ("no_cache", native_field(request, "no_cache")?), - ], - )?; - let index: Bound<'_, PyAny> = loader.call((), Some(&options))?; let lookup: Bound<'_, PyAny> = build_scope_lookup(py, &index)?.into_bound(py); let target: Option = qualify_target(py, &lookup, native_field(request, "target")?.extract()?)?; 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 da1ef161b3..80cdf70d97 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_loading.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_loading.rs @@ -171,7 +171,7 @@ fn tolerant_index<'py>( /// Load or build scope facts without credentials, a connection, or warehouse access. #[pyfunction] #[pyo3(signature = (*, project_dir, no_cache=false))] -fn load_or_build_scope_index<'py>( +pub(crate) fn load_or_build_scope_index<'py>( project_dir: &Bound<'py, PyAny>, no_cache: bool, ) -> PyResult> { From afa34da486790efce6b7a555872cb14cb7b76640 Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Sat, 17 Oct 2026 04:18:35 +0100 Subject: [PATCH 12/28] refactor: resolve native record field defaults through the captured classes --- .../sqlbuild-python/src/bindings/_helpers/records/fields.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/records/fields.rs b/crates/sqlbuild-python/src/bindings/_helpers/records/fields.rs index f1dfd120ca..b722ead7da 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/records/fields.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/records/fields.rs @@ -16,8 +16,8 @@ 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::construct::{enum_member, new_record}; 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 { @@ -166,8 +166,8 @@ fn default_value<'py>( FieldDefault::EmptyFrozenset => Ok(PyFrozenSet::empty(py)?.into_any()), FieldDefault::Member(module, class, member) => imported(py, module, class)?.getattr(member), 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::NativeMember(class, member) => enum_member(py, class, member), + FieldDefault::NativeCall(class) => new_record(py, class, (), None), FieldDefault::Factory(factory) => factory(py), FieldDefault::CallText(module, name, text) => imported(py, module, name)?.call1((text,)), } From 10245f84cbed251693900f03449fbc2b32182dfc Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Sat, 17 Oct 2026 01:37:02 +0100 Subject: [PATCH 13/28] =?UTF-8?q?refactor:=20construct=20native=20records?= =?UTF-8?q?=20directly=20in=20attachments=20=E2=80=A6=20command=5Fruntime?= =?UTF-8?q?=20bindings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace per-use sqlbuild._native class lookups with records::construct helpers (record construction, enum member and value lookup). --- .../_helpers/audit_command/command.rs | 8 ++++--- .../bindings/_helpers/auditing/identity.rs | 6 ++--- .../_helpers/build_command/completion.rs | 7 +++--- .../_helpers/build_command/defer_clone.rs | 18 ++++++++------ .../_helpers/build_command/execution.rs | 3 ++- .../_helpers/build_command/invocation.rs | 8 ++++--- .../_helpers/build_command/lifecycle.rs | 15 ++++++------ .../_helpers/build_command/planning.rs | 6 +++-- .../bindings/_helpers/check/check_command.rs | 24 ++++++++++--------- .../src/bindings/_helpers/cli/argv.rs | 14 +++++------ .../src/bindings/_helpers/cli/dispatch.rs | 9 +++---- .../src/bindings/_helpers/cli/entry.rs | 3 ++- .../src/bindings/_helpers/cli/namespace.rs | 8 +++---- .../bindings/_helpers/clone/clone_command.rs | 11 +++++---- .../_helpers/clone/clone_execution.rs | 19 ++++++++------- .../bindings/_helpers/clone/clone_pipeline.rs | 11 ++++----- .../_helpers/command_runtime/invocation.rs | 6 +++-- .../command_runtime/warehouse_scope.rs | 17 ++++++------- .../_helpers/command_runtime/warehouses.rs | 6 +++-- 19 files changed, 110 insertions(+), 89 deletions(-) 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 e0550e59f0..3d62d9276b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/audit_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/audit_command/command.rs @@ -15,6 +15,7 @@ use crate::bindings::_helpers::command_runtime::invocation::resolve_reported_com 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::construct::{new_record_with, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -114,8 +115,9 @@ fn compile_audits<'py>( ("discovered_inputs", discovered.clone()), ], )?; - let options: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::CompilePipelineOptions)?, + let options: Bound<'py, PyAny> = new_record_with( + py, + RecordId::CompilePipelineOptions, &[ ("selected_target", native_field(request, "selected_target")?), ( @@ -496,7 +498,7 @@ fn complete( fn run_audit_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { let invocation: Bound<'_, PyAny> = resolve_reported_command_invocation( request, - ®istry::class(py, RecordId::AuditInvocation)?, + &record_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 b7b2dbef43..10daf8c960 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/auditing/identity.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/auditing/identity.rs @@ -1,8 +1,8 @@ //! Audit gate identities of planned audits, built as `AuditGateIdentity` objects. +use crate::bindings::_helpers::records::construct::record_class; use crate::bindings::_helpers::records::fields::field as native_field; 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}; @@ -95,7 +95,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 identity_type: Bound<'py, PyAny> = registry::class(py, RecordId::AuditIdentity)?; + let identity_type: Bound<'py, PyAny> = record_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]; @@ -140,7 +140,7 @@ fn build_audit_gate_identity<'py>( )?); } keyword_call( - ®istry::class(py, RecordId::AuditGateIdentity)?, + &record_class(py, RecordId::AuditGateIdentity)?, vec![ ( "binding_set_hash", 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 be58089b78..494e2b82cd 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/completion.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/completion.rs @@ -26,6 +26,7 @@ use crate::bindings::_helpers::cost_report::degradation::{ }; use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -78,10 +79,8 @@ pub(crate) fn finalize_build_cost<'py>( ("use_color", boolean(py, context.use_color)), ]; fields.extend(cost.overrides); - let finalization: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::BuildCostFinalization)?, - &fields, - )?; + let finalization: Bound<'py, PyAny> = + new_record_with(py, RecordId::BuildCostFinalization, &fields)?; finalize_cost(&finalization) } 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 147b8f8056..ca77b10e23 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 @@ -17,6 +17,7 @@ use crate::bindings::_helpers::compile_driver::project::build_compiled_project; 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::construct::{enum_member, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -262,7 +263,7 @@ fn clone_input<'py>( )?; let allow_decrease: bool = target .getattr("time_travel_retention_decrease")? - .eq(registry::class(py, RecordId::RetentionDecreasePolicy)?.getattr("ALLOW")?)?; + .eq(enum_member(py, RecordId::RetentionDecreasePolicy, "ALLOW")?)?; let sources: Bound<'py, PyAny> = call_with( &host::entry(py, ExecutorHost::CloneSourceEntries)?, &[ @@ -436,8 +437,9 @@ fn clone_boundaries<'py>( ("destination_target_name", prephase.destination.clone()), ( "destination_connection", - call_with( - ®istry::class(py, RecordId::ClonePipelineConnection)?, + new_record_with( + py, + RecordId::ClonePipelineConnection, &[ ( "config", @@ -449,8 +451,9 @@ fn clone_boundaries<'py>( ), ( "options", - call_with( - ®istry::class(py, RecordId::ClonePipelineOptions)?, + new_record_with( + py, + RecordId::ClonePipelineOptions, &[ ( "no_sql_validation", @@ -601,8 +604,9 @@ pub(crate) fn run_defer_clone_boundary_prephase( ), ( "analysis_selection", - call_with( - ®istry::class(py, RecordId::CompileAnalysisSelection)?, + new_record_with( + py, + RecordId::CompileAnalysisSelection, &[("no_cache", native_field(request, "no_cache")?)], )?, ), 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 a3a257648c..992cb1edd1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/execution.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/execution.rs @@ -24,6 +24,7 @@ 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::construct::new_record; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -186,7 +187,7 @@ fn cursors<'py>( )?; } if !overrides.is_truthy()? { - overrides = registry::class(py, RecordId::CursorOverrides)?.call0()?; + overrides = new_record(py, RecordId::CursorOverrides, (), None)?; } Ok([ cursor_timestamp(&native_field(&overrides, "start_ts")?)?, 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 d7756831c6..49e84709ec 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/invocation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/invocation.rs @@ -14,6 +14,7 @@ use crate::bindings::_helpers::compile_driver::discovery::discover_project_input use crate::bindings::_helpers::compile_driver::python_calls::keywords; 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::construct::{new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -77,7 +78,7 @@ fn execution_limits<'py>( ) -> PyResult> { let py: Python<'py> = discovered.py(); let limits: Bound<'py, PyAny> = if target_name.is_none() { - registry::class(py, RecordId::ExecutionLimitsConfig)?.call0()? + new_record(py, RecordId::ExecutionLimitsConfig, (), None)? } else { native_field(&target_config(discovered, target_name)?, "execution_limits")? }; @@ -209,8 +210,9 @@ pub(crate) fn resolve_build_context<'py>( } else { load_sources.is_truthy()? }; - let invocation: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::BuildInvocation)?, + let invocation: Bound<'py, PyAny> = new_record_with( + 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 5637fc9624..b23402a663 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/lifecycle.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/lifecycle.rs @@ -18,6 +18,7 @@ use crate::bindings::_helpers::compile_driver::python_calls::{call_with, importe 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::construct::{enum_member, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -303,8 +304,9 @@ fn lifecycle_plan<'py>( let py: Python<'py> = discovered.py(); let plan_output: Bound<'py, PyAny> = native_field(pipeline, "plan_output")?; let graph: Bound<'py, PyAny> = python_node_graph(discovered)?; - let selection: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::PythonSqlRunSelection)?, + let selection: Bound<'py, PyAny> = new_record_with( + py, + RecordId::PythonSqlRunSelection, &[ ( "sql_keys", @@ -321,13 +323,12 @@ fn lifecycle_plan<'py>( ®istry::native(py, NativeAttr::BuildPythonSqlRunLifecycle)?, &[("selection", selection), ("python_graph", graph.clone())], )?; - let source_type: Bound<'py, PyAny> = - registry::class(py, RecordId::CompiledResourceType)?.getattr("SOURCE")?; - let key_class: Bound<'py, PyAny> = registry::class(py, RecordId::CompiledObjectKey)?; + let source_type: Bound<'py, PyAny> = enum_member(py, RecordId::CompiledResourceType, "SOURCE")?; let mut keys: Vec> = Vec::new(); for name in native_field(&plan, "ingress_loader_names")?.try_iter()? { - keys.push(call_with( - &key_class, + keys.push(new_record_with( + py, + RecordId::CompiledObjectKey, &[("name", name?), ("resource_type", source_type.clone())], )?); } 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 94469a8dc2..5ec04db0ed 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/planning.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/planning.rs @@ -17,6 +17,7 @@ use crate::bindings::_helpers::cli::progress_text::with_verbose_inspection; 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::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -46,8 +47,9 @@ fn request_options<'py>(context: &BuildContext<'py>) -> PyResult) -> Bound<'_, PyAny> { /// Refuse `+`, `~` and path selectors, splitting selectors as Python's `split` does. fn refuse_graph_selectors(py: Python<'_>, selectors: &[Bound<'_, PyAny>]) -> PyResult<()> { - let path_selector: Bound<'_, PyAny> = registry::class(py, RecordId::PathSelector)?; + let path_selector: Bound<'_, PyAny> = record_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()? { @@ -199,9 +200,7 @@ fn compile_pipeline<'py>( } } let loaders: Bound<'py, PyFrozenSet> = PyFrozenSet::new(py, loaders)?; - let key_class: Bound<'py, PyAny> = registry::class(py, RecordId::CompiledObjectKey)?; - let source_type: Bound<'py, PyAny> = - registry::class(py, RecordId::CompiledResourceType)?.getattr("SOURCE")?; + let source_type: Bound<'py, PyAny> = enum_member(py, RecordId::CompiledResourceType, "SOURCE")?; let mut keys: Vec> = Vec::new(); for reference in required_refs.try_iter()? { keys.push(sql_ref_key(py, &reference?)?); @@ -210,8 +209,9 @@ fn compile_pipeline<'py>( for source in native_field(&source_file?, "source_entries")?.try_iter()? { let source: Bound<'py, PyAny> = source?; if loaders.contains(native_field(&source, "loader")?)? { - keys.push(call_with( - &key_class, + keys.push(new_record_with( + py, + RecordId::CompiledObjectKey, &[ ("resource_type", source_type.clone()), ("name", native_field(&source, "name")?), @@ -225,8 +225,9 @@ fn compile_pipeline<'py>( native_field(invocation, "effective_project_dir")?, discovered.clone(), )?; - let options: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::CompilePipelineOptions)?, + let options: Bound<'py, PyAny> = new_record_with( + py, + RecordId::CompilePipelineOptions, &[ ("selected_target", native_field(request, "selected_target")?), ( @@ -318,8 +319,9 @@ fn prepare<'py>( check_functions.push(check); } } - let selection: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::PythonSqlRunSelection)?, + let selection: Bound<'py, PyAny> = new_record_with( + py, + RecordId::PythonSqlRunSelection, &[ ("sql_keys", PyFrozenSet::empty(py)?.into_any()), ("python_node_names", PyFrozenSet::empty(py)?.into_any()), @@ -652,7 +654,7 @@ fn complete<'py>( fn run_check_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { let invocation: Bound<'_, PyAny> = resolve_reported_command_invocation( request, - ®istry::class(py, RecordId::CheckInvocation)?, + &record_class(py, RecordId::CheckInvocation)?, true, )?; let stream: Bound<'_, PyAny> = native_field(&invocation, "progress_stream")?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cli/argv.rs b/crates/sqlbuild-python/src/bindings/_helpers/cli/argv.rs index 2d50199dca..4f8f227a0a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cli/argv.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cli/argv.rs @@ -13,10 +13,9 @@ 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; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; const INT_CONVERTER: &str = "int"; const POSITIVE_INTEGER_CONVERTER: &str = "_positive_integer"; @@ -258,10 +257,10 @@ 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> = registry::class(py, RecordId::ParsedCliInvocation)?; let Some(rows) = rows else { - return call_with( - &parsed, + return new_record_with( + py, + RecordId::ParsedCliInvocation, &[ ("args", py.None().into_bound(py)), ("exit_code", exit_code.into_bound_py_any(py)?), @@ -271,8 +270,9 @@ pub(crate) fn parse_cli_invocation<'py>( for (name, value) in rows { args.setattr(name.as_str(), value)?; } - call_with( - &parsed, + new_record_with( + py, + RecordId::ParsedCliInvocation, &[("args", args), ("exit_code", py.None().into_bound(py))], ) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cli/dispatch.rs b/crates/sqlbuild-python/src/bindings/_helpers/cli/dispatch.rs index 33e2a625d5..8b1f0900e8 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cli/dispatch.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cli/dispatch.rs @@ -5,6 +5,7 @@ use std::path::PathBuf; use crate::bindings::_helpers::cli::launch_state::withhold_launch_record; use crate::bindings::_helpers::command_runtime::warehouse_scope::command_warehouse_scope; use crate::bindings::_helpers::diff_report::support::cli_error; +use crate::bindings::_helpers::records::construct::{new_record_with, record_class}; use pyo3::IntoPyObjectExt; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyString, PyTuple}; @@ -176,7 +177,7 @@ fn path_class(py: Python<'_>) -> PyResult> { } fn class_object<'py>(py: Python<'py>, class: ClassRef) -> PyResult> { - registry::class(py, registry::record_named(class.name)?) + record_class(py, registry::record_named(class.name)?) } fn tuple_of<'py>(value: &Bound<'py, PyAny>) -> PyResult> { @@ -217,7 +218,6 @@ fn selector_inputs<'py>( .getattr("select")? .try_iter()? .collect::>()?; - let summary_class: Bound<'py, PyAny> = registry::class(py, RecordId::SelectorFileSummary)?; let mut files: Vec> = Vec::new(); for raw_path in native_field(args, "select_file")?.try_iter()? { let path: Bound<'py, PyAny> = path_class(py)?.call1((raw_path?,))?; @@ -236,8 +236,9 @@ fn selector_inputs<'py>( selectors.push(PyString::new(py, stripped).into_any()); count += 1; } - files.push(call_with( - &summary_class, + files.push(new_record_with( + py, + RecordId::SelectorFileSummary, &[ ("path", path), ("selector_count", count.into_bound_py_any(py)?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cli/entry.rs b/crates/sqlbuild-python/src/bindings/_helpers/cli/entry.rs index e6a518878e..6f4eb630b7 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cli/entry.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cli/entry.rs @@ -22,6 +22,7 @@ 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::construct::record_class; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -164,7 +165,7 @@ fn write_compile_machine_error( .getattr_opt("location")? .unwrap_or_else(|| py.None().into_bound(py)); let location: Bound<'_, PyAny> = - if location.is_instance(®istry::class(py, RecordId::SourceLocation)?)? { + if location.is_instance(&record_class(py, RecordId::SourceLocation)?)? { location } else { py.None().into_bound(py) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cli/namespace.rs b/crates/sqlbuild-python/src/bindings/_helpers/cli/namespace.rs index 777b09895a..ef49a74deb 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cli/namespace.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cli/namespace.rs @@ -7,8 +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::records::construct::enum_member; 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)] @@ -23,11 +23,9 @@ fn default_object(py: Python<'_>, default: NamespaceDefault) -> PyResult Ok(PyList::empty(py).into_any()), NamespaceDefault::EmptyDict => Ok(PyDict::new(py).into_any()), NamespaceDefault::CompileLineageFast => { - registry::class(py, RecordId::CompileLineageMode)?.getattr("FAST") - } - NamespaceDefault::ColumnLineageRich => { - registry::class(py, RecordId::ColumnLineageMode)?.getattr("RICH") + enum_member(py, RecordId::CompileLineageMode, "FAST") } + NamespaceDefault::ColumnLineageRich => enum_member(py, RecordId::ColumnLineageMode, "RICH"), } } 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 0861ce3202..d266fa5af8 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_command.rs @@ -30,6 +30,7 @@ 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::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -256,12 +257,14 @@ fn prepare<'py>( ("discovered_inputs", invocation.discovered.clone()), ], )?; - let destination_connection: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::ClonePipelineConnection)?, + let destination_connection: Bound<'py, PyAny> = new_record_with( + py, + RecordId::ClonePipelineConnection, &[("config", config.clone()), ("handle", connection.clone())], )?; - let options: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::ClonePipelineOptions)?, + let options: Bound<'py, PyAny> = new_record_with( + py, + RecordId::ClonePipelineOptions, &[ ( "no_sql_validation", 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 5b3bb5f04e..d03768ef96 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_execution.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_execution.rs @@ -15,6 +15,7 @@ use crate::bindings::_helpers::clone::clone_progress::{ 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::construct::{enum_member, new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -120,11 +121,7 @@ fn dependency_locations<'py>( } else { native_field(&plan, "source_map")? }; - let key_class: Bound<'py, PyAny> = registry::class(py, RecordId::CompiledObjectKey)?; - let location_class: Bound<'py, PyAny> = - registry::class(py, RecordId::CompiledRelationLocation)?; - let source_type: Bound<'py, PyAny> = - registry::class(py, RecordId::CompiledResourceType)?.getattr("SOURCE")?; + let source_type: Bound<'py, PyAny> = enum_member(py, RecordId::CompiledResourceType, "SOURCE")?; for pair in source_map.call_method0("items")?.try_iter()? { let (name, source): (Bound<'py, PyAny>, Bound<'py, PyAny>) = pair?.extract()?; let table: Bound<'py, PyAny> = native_field(&source, "table")?; @@ -144,11 +141,15 @@ fn dependency_locations<'py>( table }; let [database, schema, table] = place; - let location: Bound<'py, PyAny> = call_with( - &location_class, + let location: Bound<'py, PyAny> = new_record_with( + py, + RecordId::CompiledRelationLocation, &[database, schema, table, ("qualified_name", qualified)], )?; - locations.set_item(key_class.call1((&source_type, name))?, location)?; + locations.set_item( + new_record(py, RecordId::CompiledObjectKey, (&source_type, name), None)?, + location, + )?; } Ok(locations) } @@ -209,7 +210,7 @@ fn execution_input<'py>( )?; let allow_decrease: bool = config .getattr("time_travel_retention_decrease")? - .eq(registry::class(py, RecordId::RetentionDecreasePolicy)?.getattr("ALLOW")?)?; + .eq(enum_member(py, RecordId::RetentionDecreasePolicy, "ALLOW")?)?; let source_entries: Bound<'py, PyAny> = call_with( &host::entry(py, ExecutorHost::CloneSourceEntries)?, &[ 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 9522e6af79..8ea9db72dd 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_pipeline.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_pipeline.rs @@ -9,6 +9,7 @@ use crate::bindings::_helpers::compile_driver::project::build_compiled_project; use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::contracts::spec_resolution::resolve_target_config; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -59,8 +60,9 @@ fn compiled_environment<'py>( ), ( "analysis_selection", - call_with( - ®istry::class(py, RecordId::CompileAnalysisSelection)?, + new_record_with( + py, + RecordId::CompileAnalysisSelection, &[("no_cache", native_field(options, "no_cache")?)], )?, ), @@ -137,10 +139,7 @@ pub(crate) fn prepared_clone_pipeline<'py>( for (index, name) in PLANNED_FIELDS.iter().enumerate() { fields.push((*name, planned.get_item(index)?)); } - call_with( - ®istry::class(py, RecordId::ClonePipelineResult)?, - &fields, - ) + new_record_with(py, RecordId::ClonePipelineResult, &fields) } /// Run a clone pipeline with explicit compilation controls. 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 a43dcb0451..e5b01ad927 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/invocation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/invocation.rs @@ -8,6 +8,7 @@ use crate::bindings::_helpers::compile_driver::discovery::discover_project_input use crate::bindings::_helpers::compile_driver::python_calls::keywords; 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::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -54,8 +55,9 @@ pub(crate) fn resolve_adapter_connection_context<'py>( ("require_connection", boolean(py, require_connection)), ], )?; - call_with( - ®istry::class(py, RecordId::AdapterConnectionContext)?, + new_record_with( + py, + RecordId::AdapterConnectionContext, &[ ("adapter_name", adapter_name), ("adapter", adapter), 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 4806e45ac7..a6e3b7eac9 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,7 +9,7 @@ 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; +use crate::bindings::_helpers::records::construct::{enum_from_value, new_record_with}; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -65,8 +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 = registry::class(py, RecordId::CliCommand)? - .call1((command,))? + let name: String = enum_from_value(py, RecordId::CliCommand, command)? .str()? .to_string(); match COMMAND_WAREHOUSE_GROUPS @@ -74,7 +73,7 @@ pub(crate) fn command_warehouse_scope<'py>( .find(|(candidate, _)| *candidate == name) .and_then(|(_, group)| *group) { - Some(group) => registry::class(py, RecordId::WarehouseGroup)?.call1((group,))?, + Some(group) => enum_from_value(py, RecordId::WarehouseGroup, group)?, None => py.None().into_bound(py), } }; @@ -82,8 +81,9 @@ pub(crate) fn command_warehouse_scope<'py>( Some(cli_vars) => py.get_type::().call1((cli_vars,))?, None => PyDict::new(py).into_any(), }; - let scope: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::CommandWarehouseScope)?, + let scope: Bound<'py, PyAny> = new_record_with( + py, + RecordId::CommandWarehouseScope, &[ ("group", group), ("cli_warehouse", cli_warehouse.clone()), @@ -103,8 +103,9 @@ pub(crate) fn current_command_warehouse_scope(py: Python<'_>) -> PyResult( warehouse: Option<&str>, source: &str, ) -> PyResult> { - call_with( - ®istry::class(py, RecordId::ResolvedWarehouse)?, + new_record_with( + py, + RecordId::ResolvedWarehouse, &[ ( "warehouse", From 35ab909855fab73863ff5732e3bcccb2635d3a50 Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Sat, 17 Oct 2026 01:37:03 +0100 Subject: [PATCH 14/28] =?UTF-8?q?refactor:=20construct=20native=20records?= =?UTF-8?q?=20directly=20in=20compile=5Fattachment=20=E2=80=A6=20compute?= =?UTF-8?q?=5Flogs=20bindings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace per-use sqlbuild._native class lookups with records::construct helpers (record construction, enum member and value lookup). --- .../compile_attachment/cursor_roles.rs | 8 +- .../compile_attachment/declaration_scope.rs | 6 +- .../compile_attachment/effective_config.rs | 5 +- .../compile_attachment/model_config.rs | 12 +-- .../compile_attachment/model_inputs.rs | 24 ++--- .../sql_expansion_context.rs | 17 ++-- .../compile_attachment/storage_policies.rs | 21 ++-- .../_helpers/compile_command/report.rs | 13 ++- .../_helpers/compile_command/target_writer.rs | 11 ++- .../compile_command/test_artifact_cache.rs | 6 +- .../_helpers/compile_driver/analysis.rs | 31 +++--- .../_helpers/compile_driver/analysis_cache.rs | 11 +-- .../compile_driver/assembly_analysis.rs | 10 +- .../compile_driver/assembly_checks.rs | 29 +++--- .../_helpers/compile_driver/assembly_facts.rs | 8 +- .../compile_driver/assembly_models.rs | 13 +-- .../compile_driver/assembly_project.rs | 4 +- .../compile_driver/assembly_sql_tests.rs | 5 +- .../_helpers/compile_driver/command.rs | 9 +- .../_helpers/compile_driver/deferral.rs | 14 +-- .../bindings/_helpers/compile_driver/diff.rs | 4 +- .../_helpers/compile_driver/discovery.rs | 7 +- .../_helpers/compile_driver/inputs.rs | 12 ++- .../compile_driver/integration_loaders.rs | 11 +-- .../_helpers/compile_driver/phases.rs | 18 ++-- .../_helpers/compile_driver/pipeline.rs | 23 +++-- .../compile_driver/pipeline_entries.rs | 30 +++--- .../_helpers/compile_driver/plan_result.rs | 30 +++--- .../_helpers/compile_driver/project.rs | 14 +-- .../compile_driver/python_plan_entries.rs | 39 ++++---- .../compile_driver/selection_phases.rs | 31 +++--- .../compile_driver/target_defaults.rs | 7 +- .../_helpers/compiled_project/attachment.rs | 40 ++++---- .../compiled_project/expanded_offsets.rs | 4 +- .../_helpers/compiled_project/loop_classes.rs | 96 +++++++++---------- .../_helpers/compiled_project/records.rs | 19 ++-- 36 files changed, 334 insertions(+), 308 deletions(-) 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 1e664b7d8f..1df15a5b7a 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,11 +4,10 @@ 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; use crate::bindings::_helpers::compiled_project::model_input_view::input_reference_rows; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; 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"; @@ -95,8 +94,9 @@ pub(crate) fn resolve_cursor_input_roles<'py>( } } } - call_with( - ®istry::class(py, RecordId::CursorInputRoles)?, + new_record_with( + 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 227025419c..7564bc8522 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 @@ -6,6 +6,7 @@ use pyo3::types::PyTuple; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; use crate::bindings::_helpers::compile_driver::stage_work::{DECLARATION_SCOPES, count_stage}; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -30,8 +31,9 @@ fn scope_build<'py>( let py: Python<'py> = discovered.py(); let resolver: Bound<'py, PyAny> = declaration_scope_resolver(discovered, &index, (loaded_macros, &lookup))?; - call_with( - ®istry::class(py, RecordId::DeclarationScopeBuild)?, + new_record_with( + py, + RecordId::DeclarationScopeBuild, &[ ("loaded_macros", loaded_macros.clone()), ("index", index), 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 464ffc2611..75fcf19281 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,6 +13,7 @@ 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::construct::{new_record, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -60,7 +61,7 @@ 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> = registry::class(py, RecordId::SettingsConfig)?; + let settings_class: Bound<'py, PyAny> = record_class(py, RecordId::SettingsConfig)?; let names: Vec> = imported(py, "dataclasses", "fields")? .call1((&settings_class,))? .try_iter()? @@ -76,7 +77,7 @@ pub(crate) fn build_effective_settings<'py>( PyList::new(py, names)?, ))? .downcast_into()?; - settings_class.call((), Some(&values)) + new_record(py, RecordId::SettingsConfig, (), Some(&values)) } /// Merge effective vars using the locked precedence order. 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 f8c3245c49..3195beb87c 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 @@ -5,13 +5,12 @@ 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; use crate::bindings::_helpers::compiled_project::loop_classes::table_backed; use crate::bindings::_helpers::model_config::model_config_builder::NativeModelConfigBuilder; use crate::bindings::_helpers::model_config::unicode_environment::unicode_environment; +use crate::bindings::_helpers::records::construct::{new_record, new_record_with, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; /// Capture the project inputs every model's native config build and validation read. #[pyfunction] @@ -27,7 +26,7 @@ pub(crate) fn native_model_config_session<'py>( let materialization_defaults: Bound<'py, PyAny> = if declared.is_truthy()? { declared } else { - registry::class(py, RecordId::MaterializationDefaultsConfig)?.call0()? + new_record(py, RecordId::MaterializationDefaultsConfig, (), None)? }; let hook_entries: Bound<'py, PyTuple> = PyTuple::new( py, @@ -37,7 +36,7 @@ pub(crate) fn native_model_config_session<'py>( RecordId::PythonHookEntry, ] .iter() - .map(|class| registry::class(py, *class)) + .map(|class| record_class(py, *class)) .collect::>>()?, )?; let target_namespace: Option<(Bound<'py, PyAny>, Bound<'py, PyAny>)> = if target.is_none() { @@ -78,8 +77,9 @@ pub(crate) fn native_model_config_session<'py>( inherited_storage(&materialized, &materialization_defaults, &target)?; inherited.set_item(&materialized, storage)?; } - call_with( - ®istry::class(py, RecordId::NativeModelConfigSession)?, + new_record_with( + py, + RecordId::NativeModelConfigSession, &[ ( "path_defaults", 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 f50de01af2..dcc2f63095 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 @@ -17,6 +17,9 @@ use crate::bindings::_helpers::compiled_project::project::NativeCompiledProject; use crate::bindings::_helpers::discovery::model_file_record::NativeModelFile; use crate::bindings::_helpers::model_config::contract_classes::contract_classes; use crate::bindings::_helpers::model_config::unicode_environment::unicode_environment; +use crate::bindings::_helpers::records::construct::{ + enum_member, new_record, new_record_with, record_class, +}; use crate::bindings::_helpers::render::declaration_values::ConstantRendering; use crate::bindings::_helpers::render::macro_expansion_result::macro_expansion_classes; @@ -89,7 +92,7 @@ fn macro_inputs<'py>(context: &Bound<'py, PyAny>) -> PyResult ), ( "declaration_context", - registry::class(py, RecordId::DeclarationResolutionContext)?, + record_class(py, RecordId::DeclarationResolutionContext)?, ), ( "macro_argument_error", @@ -98,9 +101,7 @@ fn macro_inputs<'py>(context: &Bound<'py, PyAny>) -> PyResult ("expansion", macro_expansion_classes(py)?.into_any()), ( "generic_syntax", - registry::class(py, RecordId::SqlLexicalSyntax)? - .call0()? - .getattr("native_mapping")?, + new_record(py, RecordId::SqlLexicalSyntax, (), None)?.getattr("native_mapping")?, ), ], ) @@ -116,11 +117,11 @@ fn sql_hook_inputs<'py>( vec![ ( "named_sql_hook_entry", - registry::class(py, RecordId::NamedSqlHookEntry)?, + record_class(py, RecordId::NamedSqlHookEntry)?, ), ( "python_hook_entry", - registry::class(py, RecordId::PythonHookEntry)?, + record_class(py, RecordId::PythonHookEntry)?, ), ("hook_files", native_field(discovered, "sql_hook_files")?), ("effective_vars", native_field(context, "effective_vars")?), @@ -136,11 +137,11 @@ fn sql_hook_inputs<'py>( ("explicit_kinds", explicit_reference_kinds(py)?.into_any()), ( "declaration_identity", - registry::class(py, RecordId::DeclarationIdentity)?, + record_class(py, RecordId::DeclarationIdentity)?, ), ( "sql_hook_kind", - registry::class(py, RecordId::DeclarationKind)?.getattr("SQL_HOOK")?, + enum_member(py, RecordId::DeclarationKind, "SQL_HOOK")?, ), ], ) @@ -194,8 +195,9 @@ pub(crate) fn build_model_inputs<'py>( imported(py, "pathlib", "Path")?.call0()? }; let project_config_path: Bound<'py, PyAny> = root.div(PROJECT_CONFIG_FILENAME)?; - let config_inputs: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::NativeModelConfigInputs)?, + let config_inputs: Bound<'py, PyAny> = new_record_with( + py, + RecordId::NativeModelConfigInputs, &[ ( "project_config", @@ -233,7 +235,7 @@ pub(crate) fn build_model_inputs<'py>( ), ( "visible_declarations", - registry::class(py, RecordId::VisibleModelDeclarations)?, + record_class(py, RecordId::VisibleModelDeclarations)?, ), ("config_session", session), ("syntax_help", opt_out_help(py, true)?), 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 0788d4989d..3484f387e3 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 @@ -13,6 +13,7 @@ use crate::bindings::_helpers::contracts::spec_resolution::resolve_effective_ada use crate::bindings::_helpers::contracts::spec_resolution::resolve_effective_collection_rendering; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -48,7 +49,6 @@ 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> = 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()? { @@ -62,8 +62,9 @@ fn local_declarations<'py>(discovered: &Bound<'py, PyAny>) -> PyResult = model_file?; local.set_item( native_field(&model_file, "file_path")?, - call_with( - &context, + new_record_with( + py, + RecordId::DeclarationResolutionContext, &[ ( "enums", @@ -116,8 +117,9 @@ impl SqlExpansionInputs { /// These inputs as a `SqlExpansionContext` record. pub(crate) fn record<'py>(&self, py: Python<'py>) -> PyResult> { - call_with( - ®istry::class(py, RecordId::SqlExpansionContext)?, + new_record_with( + py, + RecordId::SqlExpansionContext, &[ ("effective_vars", self.effective_vars.bind(py).clone()), ("loaded_macros", self.loaded_macros.bind(py).clone()), @@ -201,8 +203,9 @@ pub(crate) fn sql_expansion_inputs(sources: ExpansionSources<'_>) -> PyResult = resolve_effective_collection_rendering(&project_config.clone(), &py.None().into_bound(py)) .and_then(|value| value.into_bound_py_any(py))?; - let macro_context: Bound<'_, PyAny> = call_with( - ®istry::class(py, RecordId::MacroContext)?, + let macro_context: Bound<'_, PyAny> = new_record_with( + py, + RecordId::MacroContext, &[ ( "adapter_name", 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 97e21f5ad0..9ed77d617f 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,10 +3,9 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyBool, PyString, PyTuple}; -use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::construct::{enum_member, new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; /// `value`, unless it is `None`. fn present(value: Bound<'_, PyAny>) -> Option> { @@ -14,7 +13,7 @@ fn present(value: Bound<'_, PyAny>) -> Option> { } fn source<'py>(py: Python<'py>, class: RecordId, member: &str) -> PyResult> { - registry::member(py, class, member) + enum_member(py, class, member) } /// The `materialized` defaults entry, or `None` for a model without a table-backed materialization. @@ -35,7 +34,6 @@ fn retention<'py>( target: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = materialized.py(); - 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 { @@ -56,10 +54,11 @@ fn retention<'py>( } } let Some((policy, member)) = policy else { - return resolved.call0(); + return new_record(py, RecordId::ResolvedTimeTravelRetention, (), None); }; - call_with( - &resolved, + new_record_with( + py, + RecordId::ResolvedTimeTravelRetention, &[ ("desired_days", native_field(&policy, "desired_days")?), ("unmanaged", native_field(&policy, "unmanaged")?), @@ -73,7 +72,6 @@ fn table_type<'py>( target: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = target.py(); - 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 { @@ -85,10 +83,11 @@ fn table_type<'py>( } } let Some((value, member)) = declared else { - return resolved.call0(); + return new_record(py, RecordId::ResolvedTableType, (), None); }; - call_with( - &resolved, + new_record_with( + py, + RecordId::ResolvedTableType, &[ ("value", value), ("source", source(py, class, member)?), 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 79af87bdf9..1daeafe30f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_command/report.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_command/report.rs @@ -12,9 +12,7 @@ use sqlbuild_core::cli_text::models::{ }; use crate::bindings::_helpers::compile_command::surrogates::SurrogateText; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported}; use crate::bindings::_helpers::compile_outputs::json_report::{ emit_json_report, emit_orjson_report, }; @@ -22,9 +20,9 @@ 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::construct::{new_record_with, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::semantic_shapes::coverage::semantic_coverage; const METADATA_MODULE: &str = "importlib.metadata"; @@ -782,7 +780,7 @@ fn test_resource<'py>(test: &Bound<'py, PyAny>) -> PyResult> let py: Python<'py> = test.py(); let payload: Bound<'py, PyAny> = native_field(test, "payload")?; let expected_models: Bound<'py, PyList> = - if payload.is_instance(®istry::class(py, RecordId::CompiledModelSqlTestPayload)?)? { + if payload.is_instance(&record_class(py, RecordId::CompiledModelSqlTestPayload)?)? { PyList::new( py, payload @@ -1069,8 +1067,9 @@ pub(crate) fn format_compile_error_json<'py>( native_field(&location, "column")?, ) }; - let diagnostic: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::CompilerDiagnostic)?, + let diagnostic: Bound<'py, PyAny> = new_record_with( + py, + RecordId::CompilerDiagnostic, &[ ("phase", any(py, "compile")?), ("severity", any(py, ERROR_SEVERITY)?), 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 d856b2ccb4..3db67b4ed0 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 @@ -34,6 +34,7 @@ use crate::bindings::_helpers::compile_outputs::runtime_target::{ }; use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -201,8 +202,9 @@ fn written_target<'py>( ) -> PyResult> { let py: Python<'py> = target_dir.py(); let [models, seeds, functions, audits, tests] = counts; - call_with( - ®istry::class(py, RecordId::WrittenTarget)?, + new_record_with( + py, + RecordId::WrittenTarget, &[ ("model_count", models.into_bound_py_any(py)?), ("seed_count", seeds.into_bound_py_any(py)?), @@ -482,8 +484,9 @@ fn artifact_diagnostics<'py>( let py: Python<'py> = test.py(); let mut diagnostics: Vec> = Vec::new(); for message in native_field(artifact, "error_messages")?.try_iter()? { - diagnostics.push(call_with( - ®istry::class(py, RecordId::CompilerDiagnostic)?, + diagnostics.push(new_record_with( + py, + RecordId::CompilerDiagnostic, &[ ("phase", "test".into_bound_py_any(py)?), ("severity", "error".into_bound_py_any(py)?), 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 045eab9cf4..254504c00f 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}; +use crate::bindings::_helpers::records::construct::record_class; 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,7 +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(®istry::class(py, RecordId::CompiledModelSqlTestPayload)?)? { + if test_payload.is_instance(&record_class(py, RecordId::CompiledModelSqlTestPayload)?)? { let block: Bound<'py, PyAny> = test.getattr("test_block")?; payload.set_item( "model", @@ -251,7 +251,7 @@ fn test_payload<'py>(test: &Bound<'py, PyAny>) -> PyResult> { ], )?, )?; - } else if test_payload.is_instance(®istry::class( + } else if test_payload.is_instance(&record_class( py, RecordId::CompiledDirectLogicSqlTestPayload, )?)? { 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 df662845d9..bade6855dd 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/analysis.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/analysis.rs @@ -22,9 +22,9 @@ use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_selection::selectors::select_project_graph; use crate::bindings::_helpers::planner_selection::unit_tests::reject_unit_test_selectors; +use crate::bindings::_helpers::records::construct::{enum_member, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::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; @@ -41,7 +41,7 @@ const TARGET_DIRECTORY: &str = "target"; /// `ColumnLineageMode.`. fn column_lineage_mode<'py>(py: Python<'py>, member: &str) -> PyResult> { - registry::class(py, RecordId::ColumnLineageMode)?.getattr(member) + enum_member(py, RecordId::ColumnLineageMode, member) } /// Column lineage in the compile's mode; `none` skips the analyzer. @@ -226,8 +226,9 @@ fn analyzed<'py>( }; let diagnostics: Bound<'py, PyTuple> = with_rule_diagnostics(py, &core_diagnostics, &rules_result)?; - call_with( - ®istry::class(py, RecordId::CompileAnalysis)?, + new_record_with( + py, + RecordId::CompileAnalysis, &[ ("discovered_inputs", discovered), ("adapter", adapter), @@ -275,8 +276,9 @@ fn analyzed<'py>( /// `RulesRunResult` of a compile whose rules did not run. fn empty_rules_result(py: Python<'_>) -> PyResult> { let zero: Bound<'_, PyAny> = 0_i64.into_pyobject(py)?.into_any(); - call_with( - ®istry::class(py, RecordId::RulesRunResult)?, + new_record_with( + py, + RecordId::RulesRunResult, &[ ("findings", PyTuple::empty(py).into_any()), ("evaluated_models", zero.clone()), @@ -427,8 +429,9 @@ impl<'py> Driver<'_, 'py> { .getattr("skip_contracts")? .is_truthy()?; let result: Bound<'py, PyAny> = if skip { - call_with( - ®istry::class(py, RecordId::ContractValidationResult)?, + new_record_with( + py, + RecordId::ContractValidationResult, &[("diagnostics", PyTuple::empty(py).into_any())], )? } else { @@ -519,11 +522,8 @@ fn with_rule_diagnostics<'py>( core: &Bound<'py, PyTuple>, rules_result: &Bound<'py, PyAny>, ) -> PyResult> { - let diagnostic_class: Bound<'py, PyAny> = registry::class(py, RecordId::CompilerDiagnostic)?; - let phase: Bound<'py, PyAny> = - registry::class(py, RecordId::DiagnosticPhase)?.getattr("RULE")?; - let severity: Bound<'py, PyAny> = - registry::class(py, RecordId::DiagnosticSeverity)?.getattr("ERROR")?; + let phase: Bound<'py, PyAny> = enum_member(py, RecordId::DiagnosticPhase, "RULE")?; + let severity: Bound<'py, PyAny> = enum_member(py, RecordId::DiagnosticSeverity, "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 native_field(rules_result, "findings")?.try_iter()? { @@ -533,8 +533,9 @@ fn with_rule_diagnostics<'py>( } else { PyTuple::empty(py) }; - diagnostics.push(call_with( - &diagnostic_class, + diagnostics.push(new_record_with( + py, + RecordId::CompilerDiagnostic, &[ ("phase", phase.clone()), ("severity", severity.clone()), 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 410eb0dec9..49cab95690 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 @@ -9,12 +9,10 @@ use pyo3::{pyfunction, wrap_pyfunction}; use sqlbuild_cache::digest::main::sha256_hex::sha256_hex; use crate::bindings::_helpers::compile_driver::compile_timings::record_compile_metric; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported}; use crate::bindings::_helpers::planner_identity::json_values::{COMPACT, unicode_sorted_dumps}; +use crate::bindings::_helpers::records::construct::new_record_with; 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"; @@ -73,8 +71,9 @@ pub(crate) fn analysis_cache_context<'py>( Err(error) if json_failure(py, &error) => return Ok(None), Err(error) => return Err(error), }; - let context = call_with( - ®istry::class(py, RecordId::AnalysisCacheContext)?, + let context = new_record_with( + py, + RecordId::AnalysisCacheContext, &[ ("root", root.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 29ba31d849..ab0418770f 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 @@ -25,9 +25,9 @@ use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::project_assembly::declared_relations::{ DeclaredRelations, declared_relations, }; +use crate::bindings::_helpers::records::construct::{enum_member, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::semantic_shapes::declarations::{declared_types, function_names}; const GENERIC_DIALECT: &str = "generic"; @@ -127,8 +127,7 @@ pub(crate) fn analysis_context<'py>( /// `_build_dynamic_families_by_table`: enforced contract models' dynamic column families. fn dynamic_families_by_table<'py>(inputs: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = inputs.py(); - let enforced: Bound<'py, PyAny> = - registry::class(py, RecordId::ContractPolicy)?.getattr("ENFORCED")?; + let enforced: Bound<'py, PyAny> = enum_member(py, RecordId::ContractPolicy, "ENFORCED")?; let families: Bound<'py, PyDict> = PyDict::new(py); for model_input in native_field(inputs, "model_inputs")?.try_iter()? { let model_input: Bound<'py, PyAny> = model_input?; @@ -305,8 +304,7 @@ fn record_expression_shapes<'py>( "zip() argument 2 is {relation} than argument 1" ))); } - let unknown: Bound<'py, PyAny> = - registry::class(py, RecordId::InferredNullability)?.getattr("UNKNOWN")?; + let unknown: Bound<'py, PyAny> = enum_member(py, RecordId::InferredNullability, "UNKNOWN")?; for (source_input, shape) in sources.iter().zip(shapes) { if shape.is_none() { continue; @@ -351,7 +349,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> = registry::class(py, RecordId::ColumnLineageMode)?; + let lineage_mode: Bound<'py, PyAny> = record_class(py, RecordId::ColumnLineageMode)?; let rich: bool = mode.eq(native_field(&lineage_mode, "RICH")?)?; let allow_compact: bool = mode.eq(native_field(&lineage_mode, "FAST")?)? || rich; let flags: (Bound<'py, PyAny>, Bound<'py, PyAny>) = ( 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 621e9178cc..b3a98bc28a 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,11 +4,13 @@ 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::python_calls::{call_with, imported, keywords}; +use crate::bindings::_helpers::compile_driver::python_calls::{imported, keywords}; use crate::bindings::_helpers::contracts::spec_resolution::resolve_effective_adapter_name; +use crate::bindings::_helpers::records::construct::{ + enum_member, new_record, new_record_with, record_class, +}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::render::declaration_values::declaration_scope_resolver; use crate::bindings::_helpers::render::macro_expansion_result::{ ExpansionRequest, expansion_result, @@ -39,7 +41,7 @@ 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> = native_field(test_input, "payload")?; - if !payload.is_instance(®istry::class( + if !payload.is_instance(&record_class( py, RecordId::CompileModelSqlTestInputPayload, )?)? { @@ -119,8 +121,9 @@ fn model_macro_context<'py>(inputs: &Bound<'py, PyAny>) -> PyResult( inputs: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = inputs.py(); - 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> = - registry::class(py, RecordId::CompiledResourceType)?.getattr("SQL_SCENARIO")?; + enum_member(py, RecordId::CompiledResourceType, "SQL_SCENARIO")?; let mut scenarios: Vec> = Vec::new(); for scenario_input in native_field(inputs, "scenario_inputs")?.try_iter()? { let scenario_input: Bound<'py, PyAny> = scenario_input?; let scenario_file: Bound<'py, PyAny> = native_field(&scenario_input, "scenario_file")?; let name: Bound<'py, PyAny> = native_field(&scenario_file, "name")?; - let key: Bound<'py, PyAny> = call_with( - &key_class, + let key: Bound<'py, PyAny> = new_record_with( + py, + RecordId::CompiledObjectKey, &[ ("resource_type", resource_type.clone()), ("name", name.clone()), @@ -178,7 +180,12 @@ pub(crate) fn compiled_sql_scenarios<'py>( "ownership_root", native_field(&scenario_file, "ownership_root")?, )?; - scenarios.push(scenario_class.call((), Some(&fields))?); + scenarios.push(new_record( + py, + RecordId::CompiledSqlScenario, + (), + Some(&fields), + )?); } PyTuple::new(py, scenarios) } 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 098a62cf58..60ae289112 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 @@ -6,7 +6,6 @@ use pyo3::types::{PyDict, PyDictMethods, PyFrozenSet, PyListMethods, PyTuple, Py use crate::bindings::_helpers::analysis_session::stage_functions::dynamic_contract_proofs; use crate::bindings::_helpers::compile_driver::assembly_analysis::AnalysisContext; -use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::compile_driver::stage_work::{ PIVOT_PROOFS, SESSION_PIVOT_PROOFS, count_stage, }; @@ -14,9 +13,9 @@ use crate::bindings::_helpers::project_assembly::models::{NativeModelFacts, Nati use crate::bindings::_helpers::project_assembly::native_resources::assemble_native_project_resources; use crate::bindings::_helpers::project_assembly::syntax_checks::model_syntax_checks; use crate::bindings::_helpers::project_assembly::syntax_checks::standalone_pivot_models; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use pyo3::IntoPyObjectExt; /// `NativeProjectFacts` of `inputs`: resources, syntax validity, deps and standalone pivot proofs. @@ -88,8 +87,9 @@ pub(crate) fn native_project_facts<'py>( /// The declared relation facts standalone pivot proofs read. pub(crate) fn pivot_tables<'py>(context: &AnalysisContext<'py>) -> PyResult> { let py: Python<'py> = context.profile.py(); - call_with( - ®istry::class(py, RecordId::NativePivotTables)?, + new_record_with( + py, + RecordId::NativePivotTables, &[ ( "dialect", 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 51f6e19b4d..53f77e3ee1 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 @@ -13,16 +13,15 @@ use crate::bindings::_helpers::boundary::panics::NativeCompilerError; use crate::bindings::_helpers::compile_driver::assembly_analysis::{ AnalysisContext, model_stem, selected_for_analysis, }; -use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::compiled_project::model_input_view::NativeModelInput; use crate::bindings::_helpers::hand_built::compiled_model::compiled_model as hand_built_model; use crate::bindings::_helpers::project_assembly::syntax_checks::model_placeholders; use crate::bindings::_helpers::project_assembly::syntax_validation::{ ParseOptions, validate_hook_sql_syntax, validate_sql_syntax, }; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::render::macro_calls::top_level_macro_call_names; const NATIVE_FAILURE_PREFIX: &str = "NativeCompilerError: "; @@ -183,8 +182,9 @@ fn compiled_model<'py>( ),), ); } - let analysis: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::HandBuiltAnalysis)?, + let analysis: Bound<'py, PyAny> = new_record_with( + py, + RecordId::HandBuiltAnalysis, &[ ("session", session_analysis), ("fallback", fallback), @@ -209,8 +209,9 @@ fn fallback_columns<'py>( placeholders: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = analysis_query_sql.py(); - let query: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::NativeColumnQuery)?, + let query: Bound<'py, PyAny> = new_record_with( + py, + RecordId::NativeColumnQuery, &[ ("sql", analysis_query_sql.clone()), ("mode", PyString::new(py, "parse").into_any()), 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 6e01d7a4de..b29a4cd1b7 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 @@ -26,9 +26,9 @@ use crate::bindings::_helpers::contracts::spec_resolution::{ 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::construct::new_record; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::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; use crate::bindings::_helpers::semantic_checks::stage::{ @@ -392,5 +392,5 @@ fn project_view<'py>( } let arguments: Bound<'py, PyDict> = fields.copy()?; arguments.set_item("sql_expansions", sql_expansions)?; - registry::class(py, RecordId::CompiledProject)?.call((), Some(&arguments)) + new_record(py, RecordId::CompiledProject, (), 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 0fcb82dafc..ad639f770d 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 @@ -8,6 +8,7 @@ use crate::bindings::_helpers::compile_driver::assembly_checks::test_model_query use crate::bindings::_helpers::compile_driver::diagnostic_collector::report_compile_diagnostic; use crate::bindings::_helpers::compile_driver::python_calls::{imported, keywords}; use crate::bindings::_helpers::compile_driver::stage_work::{SQL_TEST_ASSEMBLY, count_stage}; +use crate::bindings::_helpers::records::construct::record_class; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -36,7 +37,7 @@ pub(crate) fn assembled_sql_tests<'py>( )); } let mocked_input: Bound<'py, PyAny> = - registry::class(py, RecordId::CompileModelSqlTestInputPayload)?; + record_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> = native_field(&assembly, "failure")?; @@ -98,7 +99,7 @@ fn with_model_queries<'py>( ) -> PyResult> { let py: Python<'py> = test.py(); let payload: Bound<'py, PyAny> = native_field(test, "payload")?; - if !payload.is_instance(®istry::class(py, RecordId::CompiledModelSqlTestPayload)?)? { + if !payload.is_instance(&record_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 985f4ae027..654fd73631 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/command.rs @@ -27,6 +27,7 @@ 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::construct::{enum_member, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -335,8 +336,9 @@ fn analysis_options<'py>( status: &Status<'py>, ) -> PyResult> { let py: Python<'py> = request.py(); - let selection: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::CompileAnalysisSelection)?, + let selection: Bound<'py, PyAny> = new_record_with( + py, + RecordId::CompileAnalysisSelection, &[ ("select", native_field(request, "select")?), ("exclude", native_field(request, "exclude")?), @@ -365,8 +367,7 @@ fn analysis_options<'py>( /// Whether a rule finding failed the compile. fn rules_failed(analysis: &Bound<'_, PyAny>) -> PyResult { let py: Python<'_> = analysis.py(); - let rule_phase: Bound<'_, PyAny> = - registry::class(py, RecordId::DiagnosticPhase)?.getattr("RULE")?; + let rule_phase: Bound<'_, PyAny> = enum_member(py, RecordId::DiagnosticPhase, "RULE")?; for diagnostic in native_field(analysis, "diagnostics")?.try_iter()? { let diagnostic: Bound<'_, PyAny> = diagnostic?; if native_field(&diagnostic, "is_error")?.is_truthy()? 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 b71ccba1b0..6cfb4c06df 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/deferral.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/deferral.rs @@ -11,6 +11,7 @@ use sqlbuild_model_config::templates::models::DeferredSegment; use crate::bindings::_helpers::authored_values::project_vars::render_project_var_text; use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::records::construct::{new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -113,8 +114,9 @@ fn deferred_location<'py>( ("name", name.clone()), ], )?; - call_with( - ®istry::class(py, RecordId::CompiledRelationLocation)?, + new_record_with( + py, + RecordId::CompiledRelationLocation, &[ ("database", database), ("schema", schema), @@ -309,10 +311,9 @@ fn resolve_model_deferral_inputs<'py>( &[], ), )?; - 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(); + return new_record(py, RecordId::DeferralInputs, (), None); } let project: Bound<'py, PyAny> = arguments.value("project")?; let adapter: Bound<'py, PyAny> = arguments.value("adapter")?; @@ -336,8 +337,9 @@ fn resolve_model_deferral_inputs<'py>( &arguments.value("connection")?, locations.as_any(), )?; - call_with( - &inputs, + new_record_with( + py, + RecordId::DeferralInputs, &[ ("deferred_locations", locations.into_any()), ("deferred_relations", relations.into_any()), 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 533040b9f5..e595a73320 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/diff.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/diff.rs @@ -16,6 +16,7 @@ use crate::bindings::_helpers::analysis::model_reads; use crate::bindings::_helpers::compile_driver::project::build_compiled_project; use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, keywords}; use crate::bindings::_helpers::graph::views::project_lineage_views; +use crate::bindings::_helpers::records::construct::enum_member; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::ErrorId; use crate::bindings::_helpers::records::ids::RecordId; @@ -161,8 +162,7 @@ fn diff_model_names<'py>( let exclude: Vec = exclude.extract()?; let selected: HashSet = resolve_diff_selection(&graph, (&select, &exclude)) .map_err(|error| selector_error(py, error))?; - let model_type: Bound<'py, PyAny> = - registry::class(py, RecordId::CompiledResourceType)?.getattr("MODEL")?; + let model_type: Bound<'py, PyAny> = enum_member(py, RecordId::CompiledResourceType, "MODEL")?; let mut names: Vec> = Vec::new(); for model in native_field(project, "models")?.try_iter()? { let model: Bound<'py, PyAny> = model?; 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 92e7aeb24d..5b1395612e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/discovery.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/discovery.rs @@ -43,9 +43,9 @@ use crate::bindings::_helpers::frontier::compile_frontier::{FrontierStage, produ 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::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use pyo3::IntoPyObjectExt; const SQL_ANALYSIS_SETTING_KEY: &str = "sql_analysis"; @@ -485,10 +485,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( - ®istry::class(py, RecordId::DiscoveredProjectInputs)?, - &fields, - ) + new_record_with(py, RecordId::DiscoveredProjectInputs, &fields) } pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { 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 562e243f7a..66d1b07468 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/inputs.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/inputs.rs @@ -20,9 +20,9 @@ use crate::bindings::_helpers::contracts::spec_resolution::resolve_effective_ada use crate::bindings::_helpers::model_config::template_data::run_id; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::render::declaration_indexes::{ public_declaration_indexes, public_model_schema_index, }; @@ -158,8 +158,9 @@ fn compile_inputs<'py>(py: Python<'py>, request: &InputsRequest) -> PyResult = native_field(&adapter_context, "value_renderer")?; - let model_context: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::ModelInputBuildContext)?, + let model_context: Bound<'py, PyAny> = new_record_with( + py, + RecordId::ModelInputBuildContext, &[ ("effective_vars", target.vars.clone()), ("effective_settings", target.settings.clone()), @@ -275,8 +276,9 @@ fn target_context<'py>( build_effective_settings(&project_config.clone(), &local_config.clone())?; let adapter_name: Bound<'py, PyAny> = resolve_effective_adapter_name(&project_config.clone(), &local_config)?; - let macro_context: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::MacroContext)?, + let macro_context: Bound<'py, PyAny> = new_record_with( + py, + RecordId::MacroContext, &[ ("adapter_name", adapter_name), ( 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 07eb7e45d2..4169bc2566 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 @@ -8,9 +8,9 @@ use sqlbuild_discovery::yaml_entries::main::integration_loader_name::integration 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::construct::{enum_member, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; 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 { @@ -35,9 +35,7 @@ pub(crate) fn integration_loader_functions<'py>( py: Python<'py>, source_files: &Bound<'py, PyAny>, ) -> PyResult> { - let record_class: Bound<'py, PyAny> = registry::class(py, RecordId::DiscoveredLoaderFunction)?; - let external: Bound<'py, PyAny> = - registry::class(py, RecordId::LoaderConnectionMode)?.getattr("EXTERNAL")?; + let external: Bound<'py, PyAny> = enum_member(py, RecordId::LoaderConnectionMode, "EXTERNAL")?; let mut loaders: Vec> = Vec::new(); for integration in &INTEGRATIONS { let kind: String = host::entry(py, integration.kind_constant)?.extract()?; @@ -64,8 +62,9 @@ pub(crate) fn integration_loader_functions<'py>( ("name", name.clone().into_pyobject(py)?.into_any()), ], )?; - loaders.push(call_with( - &record_class, + loaders.push(new_record_with( + py, + RecordId::DiscoveredLoaderFunction, &[ ("file_path", native_field(&source_file, "file_path")?), ( 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 0c8d7bb8db..0e61ba02be 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/phases.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/phases.rs @@ -15,6 +15,7 @@ use crate::bindings::_helpers::contracts::spec_resolution::resolve_effective_ada 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::manifest::builder::build_manifest; +use crate::bindings::_helpers::records::construct::{enum_member, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -175,16 +176,14 @@ pub(crate) fn sql_test_planning_diagnostics<'py>( } else { py.None().into_bound(py) }; - let diagnostic: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::CompilerDiagnostic)?, + let diagnostic: Bound<'py, PyAny> = new_record_with( + py, + RecordId::CompilerDiagnostic, &[ - ( - "phase", - registry::class(py, RecordId::DiagnosticPhase)?.getattr("TEST")?, - ), + ("phase", enum_member(py, RecordId::DiagnosticPhase, "TEST")?), ( "severity", - registry::class(py, RecordId::DiagnosticSeverity)?.getattr("ERROR")?, + enum_member(py, RecordId::DiagnosticSeverity, "ERROR")?, ), ("code", native_field(&value, "code")?), ("message", value.str()?.into_any()), @@ -207,8 +206,9 @@ pub(crate) fn write_artifacts<'py>( let target: Bound<'py, PyAny> = target_dir(project_dir)?; let written: Bound<'py, PyAny> = if skip_write { let zero: Bound<'py, PyAny> = 0_i64.into_pyobject(py)?.into_any(); - call_with( - ®istry::class(py, RecordId::WrittenTarget)?, + new_record_with( + 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 adac9145ba..f37771ee4a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/pipeline.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/pipeline.rs @@ -14,7 +14,7 @@ use crate::bindings::_helpers::compile_driver::plan_result::{ }; use crate::bindings::_helpers::compile_driver::project::build_compiled_project; use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, keywords, + KeywordArguments, imported, keywords, }; use crate::bindings::_helpers::compile_driver::selection_phases::{ apply_unit_test_selectors, resolve_configured_rule_selection, @@ -23,6 +23,7 @@ use crate::bindings::_helpers::diagnostics::trackers::current_tracker; 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::construct::{enum_member, new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -81,7 +82,7 @@ fn compiled_phase<'py>( let options: Bound<'py, PyAny> = if options.is_truthy()? { options.clone() } else { - registry::class(py, RecordId::CompilePipelineOptions)?.call0()? + new_record(py, RecordId::CompilePipelineOptions, (), None)? }; let configured: Bound<'py, PyAny> = native_field(&options, "connection_config")?; let connection_config: Bound<'py, PyAny> = if configured.is_none() { @@ -134,8 +135,9 @@ fn compiled_phase<'py>( on_progress, &format!("Compiled project. ({compile_seconds:.2}s)"), )?; - call_with( - ®istry::class(py, RecordId::CompiledProjectPhaseResult)?, + new_record_with( + py, + RecordId::CompiledProjectPhaseResult, &[ ("project", project), ("connection_config", connection_config), @@ -155,8 +157,7 @@ fn reject_compile_errors( ) -> PyResult<()> { let py: Python<'_> = project.py(); let plan_sql_tests: bool = native_field(options, "plan_sql_tests")?.is_truthy()?; - let sql_test: Bound<'_, PyAny> = - registry::class(py, RecordId::CompiledResourceType)?.getattr("SQL_TEST")?; + let sql_test: Bound<'_, PyAny> = enum_member(py, RecordId::CompiledResourceType, "SQL_TEST")?; let mut errors: Vec> = Vec::new(); for diagnostic in native_field(project, "diagnostics")?.try_iter()? { let diagnostic: Bound<'_, PyAny> = diagnostic?; @@ -259,8 +260,9 @@ fn run_rules_gate<'py>( let mut config: Bound<'py, PyAny> = load_rules_config(py, project_dir.extract()?)?.into_bound(py); if native_field(options, "no_cache")?.is_truthy()? { - let cache: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::RulesCacheConfig)?, + let cache: Bound<'py, PyAny> = new_record_with( + py, + RecordId::RulesCacheConfig, &[("enabled", false.into_pyobject(py)?.to_owned().into_any())], )?; config = imported(py, "dataclasses", "replace")? @@ -303,8 +305,9 @@ fn run_rules_gate<'py>( if !findings.is_truthy()? { return Ok(()); } - let summary: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::RulesResult)?, + let summary: Bound<'py, PyAny> = new_record_with( + py, + RecordId::RulesResult, &[ ("findings", findings), ( 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 6ea8a42d00..5c386ea826 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 @@ -5,13 +5,11 @@ use pyo3::types::{PyDict, PyFloat}; use pyo3::{pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::compile_driver::pipeline::compile_project_phase; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, keywords, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, keywords}; use crate::bindings::_helpers::graph::project_graph::{CompiledProjectGraph, NativeProjectGraph}; +use crate::bindings::_helpers::records::construct::{new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; /// Discovered Python node functions whose selectors need the Python graph. const PYTHON_NODE_FUNCTIONS: [&str; 4] = [ @@ -62,7 +60,6 @@ pub(crate) fn compile_analysis_selection<'py>( break; } } - let selection: Bound<'py, PyAny> = registry::class(py, RecordId::CompileAnalysisSelection)?; let no_cache: Bound<'py, PyAny> = native_field(options, "no_cache")?; if (options .getattr("resolve_python_run_selectors")? @@ -73,10 +70,15 @@ pub(crate) fn compile_analysis_selection<'py>( .getattr("names")? .is_truthy()? { - return call_with(&selection, &[("no_cache", no_cache)]); + return new_record_with( + py, + RecordId::CompileAnalysisSelection, + &[("no_cache", no_cache)], + ); } - call_with( - &selection, + new_record_with( + py, + RecordId::CompileAnalysisSelection, &[ ("select", native_field(options, "select")?), ("exclude", native_field(options, "exclude")?), @@ -155,8 +157,9 @@ pub(crate) fn build_static_pipeline_result<'py>( (&["context", "plan_output"], &[]), )?; let context: Bound<'py, PyAny> = arguments.value("context")?; - call_with( - ®istry::class(py, RecordId::CompilePipelineResult)?, + new_record_with( + py, + RecordId::CompilePipelineResult, &[ ("project", native_field(&context, "project")?), ("plan_output", arguments.value("plan_output")?), @@ -197,13 +200,14 @@ pub(crate) fn run_compile_only_pipeline<'py>( ], )?), )?; - call_with( - ®istry::class(py, RecordId::CompilePipelineResult)?, + new_record_with( + py, + RecordId::CompilePipelineResult, &[ ("project", native_field(&compiled, "project")?), ( "plan_output", - registry::class(py, RecordId::PlanOutput)?.call0()?, + new_record(py, RecordId::PlanOutput, (), None)?, ), ( "compile_seconds", 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 12ced32ae3..75f90615a8 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 @@ -10,19 +10,19 @@ use crate::bindings::_helpers::compile_driver::python_plan_entries::{ }; use crate::bindings::_helpers::planner_entries::execution_plan::build_execution_plan; use crate::bindings::_helpers::planner_phases::objects::object; +use crate::bindings::_helpers::records::construct::new_record_with; 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}; +use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; 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::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; const SINGLE_CONNECTION: i64 = 1; @@ -80,8 +80,9 @@ pub(crate) fn pipeline_result<'py>( )?), )?; } - let selection: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::PlannerSelection)?, + let selection: Bound<'py, PyAny> = new_record_with( + py, + RecordId::PlannerSelection, &[ ("select", select), ("exclude", exclude), @@ -90,8 +91,9 @@ pub(crate) fn pipeline_result<'py>( ("sql_test_selection", sql_test_selection), ], )?; - let overrides: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::PlannerOverrides)?, + let overrides: Bound<'py, PyAny> = new_record_with( + py, + RecordId::PlannerOverrides, &option_fields( options, &[ @@ -110,12 +112,11 @@ pub(crate) fn pipeline_result<'py>( options, &["defer_sources_to", "source_deferral_enabled"], )?); - let deferral: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::DeferralInputs)?, - &deferral_fields, - )?; - let policies: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::PlannerPolicies)?, + let deferral: Bound<'py, PyAny> = + new_record_with(py, RecordId::DeferralInputs, &deferral_fields)?; + let policies: Bound<'py, PyAny> = new_record_with( + py, + RecordId::PlannerPolicies, &option_fields( options, &[ @@ -162,8 +163,9 @@ pub(crate) fn pipeline_result<'py>( ], )?), )?; - call_with( - ®istry::class(py, RecordId::CompilePipelineResult)?, + new_record_with( + py, + RecordId::CompilePipelineResult, &[ ("project", project.clone()), ("plan_output", native_field(&python_outputs, "plan_output")?), 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 60a2019f3a..87ec2c5cbf 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/project.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/project.rs @@ -17,6 +17,7 @@ use crate::bindings::_helpers::compile_driver::targets::{ project_targets, }; use crate::bindings::_helpers::frontier::compile_frontier::{FrontierStage, bridged, produced}; +use crate::bindings::_helpers::records::construct::{enum_member, new_record_with}; use crate::bindings::_helpers::compile_driver::assembly::assemble_project; use crate::bindings::_helpers::compile_driver::python_calls::keywords; @@ -28,7 +29,6 @@ use crate::bindings::_helpers::contracts::spec_resolution::resolve_effective_ada use crate::bindings::_helpers::contracts::spec_resolution::resolve_effective_collection_rendering; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use pyo3::IntoPyObjectExt; use sqlbuild_core::constants::PROJECT_CONFIG_FILENAME; use sqlbuild_core::constants::TABLE_PROMOTION_MODE_SETTING_KEY; @@ -64,7 +64,7 @@ pub(crate) fn lineage_mode<'py>(arguments: &KeywordArguments<'py>) -> PyResult( &py.None().into_bound(py), ) .and_then(|value| value.into_bound_py_any(py))?; - let adapter_context: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::CompileAdapterContext)?, + let adapter_context: Bound<'py, PyAny> = new_record_with( + py, + RecordId::CompileAdapterContext, &[ ("value_renderer", adapter.clone()), ("collection_rendering", collection_rendering), @@ -329,8 +330,9 @@ pub(crate) fn build_project_graph<'py>( options, (&["discovered_inputs", "adapter"], &optional), )?; - let selection: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::CompileAnalysisSelection)?, + let selection: Bound<'py, PyAny> = new_record_with( + 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 dd794cb26c..db9a00adcb 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 @@ -1,6 +1,7 @@ //! Display-ready Python plan entries and Python-aware selection warnings of a plan. use crate::bindings::_helpers::graph::python_lifecycle::build_python_sql_run_lifecycle; +use crate::bindings::_helpers::records::construct::{enum_member, new_record_with, record_class}; use std::collections::HashMap; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; @@ -18,7 +19,6 @@ use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::native_host::user_code::UserCodeHost; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; const SKIPPED_UPSTREAM_CODE: &str = "P501"; @@ -124,13 +124,13 @@ pub(crate) fn build_python_run_plan_outputs<'py>( &[], ), )?; - 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")?; if run_selection.is_none() { - return call_with( - &outputs, + return new_record_with( + py, + RecordId::PythonRunPlanOutputs, &[ ("plan_output", plan_output), ("python_plan_entries", PyTuple::empty(py).into_any()), @@ -156,8 +156,9 @@ pub(crate) fn build_python_run_plan_outputs<'py>( &[("warnings", PyTuple::new(py, warnings)?.into_any())], )?), )?; - let selection: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::PythonSqlRunSelection)?, + let selection: Bound<'py, PyAny> = new_record_with( + py, + RecordId::PythonSqlRunSelection, &[ ("sql_keys", native_field(&plan_output, "selected_keys")?), ("python_node_names", selected.clone()), @@ -169,8 +170,9 @@ pub(crate) fn build_python_run_plan_outputs<'py>( (&graph, &kinds), &native_field(&plan_output, "python_identity_fingerprints")?, )?; - call_with( - &outputs, + new_record_with( + py, + RecordId::PythonRunPlanOutputs, &[ ("plan_output", plan_output), ("python_plan_entries", entries.into_any()), @@ -215,8 +217,6 @@ fn plan_entries<'py>( previous_identities: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = lifecycle.py(); - 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> = native_field(graph, "nodes_by_name")?; let mut entries: Vec> = Vec::new(); @@ -244,12 +244,13 @@ fn plan_entries<'py>( value.getattr(name) } }; - entries.push(call_with( - &entry_class, + entries.push(new_record_with( + py, + RecordId::PythonPlanEntry, &[ ("name", native_field(&node, "name")?), ("kind", kind), - ("phase", phases.getattr(phase)?), + ("phase", enum_member(py, RecordId::PythonRunPhase, phase)?), ( "identity_status", identity_status(&identity, previous_identities)?, @@ -301,8 +302,7 @@ fn identity_status<'py>( identity: &Bound<'py, PyAny>, previous_identities: &Bound<'py, PyAny>, ) -> PyResult> { - let statuses: Bound<'py, PyAny> = - registry::class(identity.py(), RecordId::PythonIdentityStatus)?; + let statuses: Bound<'py, PyAny> = record_class(identity.py(), RecordId::PythonIdentityStatus)?; if identity.is_none() { return native_field(&statuses, "UNKNOWN"); } @@ -337,9 +337,7 @@ fn skipped_ingress_warnings<'py>( let nodes: Bound<'py, PyAny> = native_field(graph, "nodes_by_name")?; let selected: Bound<'py, PyAny> = native_field(run_selection, "python_node_names")?; let upstream: HashMap> = upstream_names(graph)?; - let warning_class: Bound<'py, PyAny> = registry::class(py, RecordId::PlanWarning)?; - let severity: Bound<'py, PyAny> = - registry::class(py, RecordId::WarningSeverity)?.getattr("WARNING")?; + let severity: Bound<'py, PyAny> = enum_member(py, RecordId::WarningSeverity, "WARNING")?; let mut warnings: Vec> = Vec::new(); for loader in loaders { if !nodes.contains(loader.as_str())? { @@ -360,8 +358,9 @@ fn skipped_ingress_warnings<'py>( +source:{loader} to refresh upstream ingress dependencies.", native_field(&kind, "value")?.str()? ); - warnings.push(call_with( - &warning_class, + warnings.push(new_record_with( + py, + RecordId::PlanWarning, &[ ("model_name", py.None().into_bound(py)), ("severity", severity.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 edde609702..8a1bb51bcc 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 @@ -11,15 +11,15 @@ use crate::bindings::_helpers::compile_driver::deferral::{ }; use crate::bindings::_helpers::compile_driver::pipeline::compile_project_phase; use crate::bindings::_helpers::compile_driver::pipeline_entries::compile_analysis_selection; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; +use crate::bindings::_helpers::compile_driver::python_calls::{imported, keywords}; use crate::bindings::_helpers::planner_entries::command_plans::{ resolve_static_command_scope, resolve_static_relation_context, }; use crate::bindings::_helpers::planner_phases::scopes::resolve_planner_scopes; use crate::bindings::_helpers::planner_selection::unit_tests::split_project_unit_test_selectors; +use crate::bindings::_helpers::records::construct::{new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; const DATACLASSES_MODULE: &str = "dataclasses"; @@ -36,7 +36,7 @@ fn unit_test_selectors_applied<'py>( ) -> PyResult> { let py: Python<'py> = discovered.py(); let resolved: Bound<'py, PyAny> = if options.is_none() { - registry::class(py, RecordId::CompilePipelineOptions)?.call0()? + new_record(py, RecordId::CompilePipelineOptions, (), None)? } else { options.clone() }; @@ -73,15 +73,17 @@ fn planner_scopes<'py>( let py: Python<'py> = project.py(); resolve_planner_scopes( project, - &call_with( - ®istry::class(py, RecordId::PlannerSelection)?, + &new_record_with( + py, + RecordId::PlannerSelection, &[ ("select", native_field(selection, "select")?), ("exclude", native_field(selection, "exclude")?), ], )?, - &call_with( - ®istry::class(py, RecordId::PlannerPolicies)?, + &new_record_with( + py, + RecordId::PlannerPolicies, &[( "auto_load_sources", native_field(selection, "auto_load_sources")?, @@ -259,8 +261,9 @@ pub(crate) fn compile_static_command_context<'py>( deferred_locations((&discovered, &adapter), &options, &project)?; let scope: Bound<'py, PyAny> = resolve_static_command_scope( &project, - &call_with( - ®istry::class(py, RecordId::PlannerSelection)?, + &new_record_with( + py, + RecordId::PlannerSelection, &[ ("select", native_field(&options, "select")?), ("exclude", native_field(&options, "exclude")?), @@ -283,8 +286,9 @@ pub(crate) fn compile_static_command_context<'py>( ("scope", scope.clone()), ( "deferral", - call_with( - ®istry::class(py, RecordId::DeferralInputs)?, + new_record_with( + py, + RecordId::DeferralInputs, &[ ("deferred_locations", deferred), ( @@ -310,8 +314,9 @@ pub(crate) fn compile_static_command_context<'py>( ], )?), )?; - call_with( - ®istry::class(py, RecordId::StaticCommandContext)?, + new_record_with( + 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 e15fb7b9c8..8541ceabc0 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 @@ -8,9 +8,9 @@ use crate::bindings::_helpers::compiled_project::compiled_model_view::NativeComp use crate::bindings::_helpers::compiled_project::function_views::NativeCompiledFunction; use crate::bindings::_helpers::compiled_project::records::project_with; use crate::bindings::_helpers::compiled_project::seed_views::NativeCompiledSeed; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; /// The adapter defaults every compiled location falls back to. pub(crate) struct TargetDefaults<'py> { @@ -177,8 +177,9 @@ fn resolved_target<'py>( { return Ok(target.clone()); } - call_with( - ®istry::class(py, RecordId::CompiledRelationLocation)?, + new_record_with( + py, + RecordId::CompiledRelationLocation, &[ ("database", database), ("schema", schema), 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 85c03a5899..5d4f09a8f9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/attachment.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/attachment.rs @@ -26,6 +26,7 @@ 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::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::construct::{enum_member, new_record_with, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -59,7 +60,7 @@ fn items<'py>(value: &Bound<'py, PyAny>, name: &str) -> PyResult, class: RecordId) -> PyResult> { - members_by_value(®istry::class(py, class)?) + members_by_value(&record_class(py, class)?) } fn members_by_value<'py>(enum_class: &Bound<'py, PyAny>) -> PyResult> { @@ -175,8 +176,7 @@ pub(crate) fn attach_resources<'py>( let defaults: Bound<'py, PyAny> = discovered_inputs .getattr("project_config")? .getattr("defaults")?; - let function_kind: Bound<'py, PyAny> = - registry::member(py, RecordId::ResourceKind, "FUNCTION")?; + let function_kind: Bound<'py, PyAny> = enum_member(py, RecordId::ResourceKind, "FUNCTION")?; let functions = NativeCompiledProject::attach_functions( native_project, ( @@ -216,7 +216,7 @@ pub(crate) fn attach_resources<'py>( ( attr(&defaults, "contract")?, validate, - registry::member(py, RecordId::ResourceKind, "SOURCE")?.unbind(), + enum_member(py, RecordId::ResourceKind, "SOURCE")?.unbind(), ), )?; PyTuple::new( @@ -336,7 +336,7 @@ fn sql_test_settings<'py>( ("scenario_input", RecordId::CompileSqlScenarioInput), ("scenario_cte", RecordId::CompileSqlScenarioCte), ] { - settings.set_item(key, registry::class(py, class)?)?; + settings.set_item(key, record_class(py, class)?)?; } settings.set_item( "modes", @@ -344,15 +344,15 @@ fn sql_test_settings<'py>( )?; settings.set_item( "test_kind", - registry::member(py, RecordId::ResourceKind, "TEST")?, + enum_member(py, RecordId::ResourceKind, "TEST")?, )?; settings.set_item( "scenario_kind", - registry::member(py, RecordId::ResourceKind, "SCENARIO")?, + enum_member(py, RecordId::ResourceKind, "SCENARIO")?, )?; settings.set_item( "runtime_usage", - registry::member(py, RecordId::UsageKind, "RUNTIME")?, + enum_member(py, RecordId::UsageKind, "RUNTIME")?, )?; settings.set_item("render_parameter", parameter_renderer(value_renderer)?)?; settings.set_item( @@ -361,7 +361,7 @@ fn sql_test_settings<'py>( )?; settings.set_item( "source_location", - registry::class(py, RecordId::SourceLocation)?, + record_class(py, RecordId::SourceLocation)?, )?; settings.set_item("scan_store", scan_cache.call_method0("opened")?)?; settings.set_item( @@ -390,7 +390,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> = registry::member(py, RecordId::DeclarationKind, "AUDIT")?; + let generic: Bound<'_, PyAny> = enum_member(py, RecordId::DeclarationKind, "AUDIT")?; Ok(( text(&file, "file_path")?, text(&file, "relative_path")?, @@ -413,16 +413,18 @@ fn builtin_audit_file<'py>( let path_class: Bound<'py, PyAny> = imported(py, PATHLIB_MODULE, "Path")?; let relative_path: Bound<'py, PyAny> = path_class.call1((format!("audits/generic/{name}.sql"),))?; - let block: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::DiscoveredAuditBlock)?, + let block: Bound<'py, PyAny> = new_record_with( + py, + RecordId::DiscoveredAuditBlock, &[ ("audit_index", 0.into_bound_py_any(py)?), ("header_values", PyDict::new(py).into_any()), ("sql_body", sql_body.into_bound_py_any(py)?), ], )?; - call_with( - ®istry::class(py, RecordId::DiscoveredAuditFile)?, + new_record_with( + py, + RecordId::DiscoveredAuditFile, &[ ( "file_path", @@ -463,15 +465,15 @@ fn audit_settings<'py>( settings.set_item("builtins", builtins)?; settings.set_item( "declaration_identity", - registry::class(py, RecordId::DeclarationIdentity)?, + record_class(py, RecordId::DeclarationIdentity)?, )?; settings.set_item( "singular_kind", - registry::member(py, RecordId::DeclarationKind, "SINGULAR_AUDIT")?, + enum_member(py, RecordId::DeclarationKind, "SINGULAR_AUDIT")?, )?; settings.set_item( "audit_kind", - registry::member(py, RecordId::DeclarationKind, "AUDIT")?, + enum_member(py, RecordId::DeclarationKind, "AUDIT")?, )?; settings.set_item( "resource_kinds", @@ -510,12 +512,12 @@ fn diagnostic_settings<'py>( let settings: Bound<'py, PyDict> = PyDict::new(py); settings.set_item( "compiler_diagnostic", - registry::class(py, RecordId::CompilerDiagnostic)?, + record_class(py, RecordId::CompilerDiagnostic)?, )?; settings.set_item("compile_error", registry::error(py, ErrorId::CompileInput)?)?; settings.set_item( "source_location", - registry::class(py, RecordId::SourceLocation)?, + record_class(py, RecordId::SourceLocation)?, )?; settings.set_item("path_class", imported(py, PATHLIB_MODULE, "Path")?)?; settings.set_item("model_files", items(discovered, "model_files")?)?; 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 6208bb7ec8..b46ae941b8 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,8 +1,8 @@ //! Map offsets in expanded SQL back onto the authored text that produced them. +use crate::bindings::_helpers::records::construct::new_record; use crate::bindings::_helpers::records::fields::field as native_field; 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}; @@ -35,7 +35,7 @@ fn map_expanded_offset<'py>( let arguments: Bound<'py, PyDict> = PyDict::new(py); arguments.set_item("offset", offset)?; arguments.set_item("generated", generated)?; - registry::class(py, RecordId::MappedOffset)?.call((), Some(&arguments)) + new_record(py, RecordId::MappedOffset, (), 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 acb029a4bb..0128c4784a 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 @@ -7,6 +7,7 @@ use pyo3::{IntoPyObjectExt, pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::native_host::facade::FacadeHost; use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::construct::{enum_member, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -31,7 +32,7 @@ fn dict<'py>( pub(crate) fn table_backed<'py>(py: Python<'py>) -> PyResult>> { TABLE_BACKED_MATERIALIZATIONS .iter() - .map(|name| registry::member(py, RecordId::MaterializationType, name)) + .map(|name| enum_member(py, RecordId::MaterializationType, name)) .collect() } @@ -56,24 +57,24 @@ pub(crate) fn native_config_classes<'py>( ("table_backed", PyTuple::new(py, table_backed)?.into_any()), ( "compile_model_config", - registry::class(py, RecordId::CompileModelConfig)?, + record_class(py, RecordId::CompileModelConfig)?, ), ( "resolved_retention", - registry::class(py, RecordId::ResolvedTimeTravelRetention)?, + record_class(py, RecordId::ResolvedTimeTravelRetention)?, ), ( "retention_source", - registry::member(py, RecordId::TimeTravelRetentionSource, "MODEL")?, + enum_member(py, RecordId::TimeTravelRetentionSource, "MODEL")?, ), ( "resolved_table_type", - registry::class(py, RecordId::ResolvedTableType)?, + record_class(py, RecordId::ResolvedTableType)?, ), - ("table_type", registry::class(py, RecordId::TableType)?), + ("table_type", record_class(py, RecordId::TableType)?), ( "table_type_source", - registry::member(py, RecordId::TableTypeSource, "MODEL")?, + enum_member(py, RecordId::TableTypeSource, "MODEL")?, ), ("record_template_reads", record_template_reads.clone()), ], @@ -89,7 +90,7 @@ fn named_records<'py>(resolver: &Bound<'py, PyAny>) -> PyResult> = NAMED_KINDS .iter() - .map(|name| registry::member(py, RecordId::DeclarationKind, name)) + .map(|name| enum_member(py, RecordId::DeclarationKind, name)) .collect::>()?; let declarations: Bound<'py, PyAny> = native_field(resolver, "lookup")?.getattr("declarations")?; @@ -119,8 +120,8 @@ fn schema_enums<'py>(resolver: &Bound<'py, PyAny>) -> PyResult = registry::member(py, RecordId::DeclarationKind, "ENUM")?; - let enum_class: Bound<'py, PyAny> = registry::class(py, RecordId::EnumDeclaration)?; + let enum_kind: Bound<'py, PyAny> = enum_member(py, RecordId::DeclarationKind, "ENUM")?; + let enum_class: Bound<'py, PyAny> = record_class(py, RecordId::EnumDeclaration)?; let declarations: Bound<'py, PyAny> = native_field(resolver, "projection")?.getattr("declarations")?; for pair in declarations.call_method0("items")?.try_iter()? { @@ -174,7 +175,7 @@ fn visibility_classes<'py>( let lookup: Bound<'py, PyAny> = native_field(&resolver, "lookup")?; let declarations: Bound<'py, PyAny> = native_field(&lookup, "index")?.getattr("declarations")?; - let private: Bound<'py, PyAny> = registry::member(py, RecordId::ScopeKind, "PRIVATE")?; + let private: Bound<'py, PyAny> = enum_member(py, RecordId::ScopeKind, "PRIVATE")?; let mut owners: Vec> = Vec::new(); for declaration in declarations.try_iter()? { let declaration: Bound<'py, PyAny> = declaration?; @@ -203,31 +204,31 @@ fn visibility_classes<'py>( ("visible_declarations", visible_declarations.clone()), ( "resource_identity", - registry::class(py, RecordId::ResourceIdentity)?, + record_class(py, RecordId::ResourceIdentity)?, ), ( "declaration_identity", - registry::class(py, RecordId::DeclarationIdentity)?, + record_class(py, RecordId::DeclarationIdentity)?, ), ( "model_kind", - registry::member(py, RecordId::ResourceKind, "MODEL")?, + enum_member(py, RecordId::ResourceKind, "MODEL")?, ), ( "enum_kind", - registry::member(py, RecordId::DeclarationKind, "ENUM")?, + enum_member(py, RecordId::DeclarationKind, "ENUM")?, ), ( "constant_kind", - registry::member(py, RecordId::DeclarationKind, "CONSTANT")?, + enum_member(py, RecordId::DeclarationKind, "CONSTANT")?, ), ( "private_owner", - registry::member(py, RecordId::VisibilityReason, "PRIVATE_OWNER")?, + enum_member(py, RecordId::VisibilityReason, "PRIVATE_OWNER")?, ), ( "visibility_record", - registry::class(py, RecordId::VisibilityRecord)?, + record_class(py, RecordId::VisibilityRecord)?, ), ], ) @@ -235,49 +236,46 @@ fn visibility_classes<'py>( fn input_view_classes<'py>(py: Python<'py>) -> PyResult> { let compiled: Vec<(&str, Bound<'py, PyAny>)> = vec![ - ( - "object_key", - registry::class(py, RecordId::CompiledObjectKey)?, - ), + ("object_key", record_class(py, RecordId::CompiledObjectKey)?), ( "relation_location", - registry::class(py, RecordId::CompiledRelationLocation)?, + record_class(py, RecordId::CompiledRelationLocation)?, ), ( "inferred_column", - registry::class(py, RecordId::InferredColumn)?, + record_class(py, RecordId::InferredColumn)?, ), ( "nullability", - registry::class(py, RecordId::InferredNullability)?, + record_class(py, RecordId::InferredNullability)?, ), ( "compiler_diagnostic", - registry::class(py, RecordId::CompilerDiagnostic)?, + record_class(py, RecordId::CompilerDiagnostic)?, ), ( "source_location", - registry::class(py, RecordId::SourceLocation)?, + record_class(py, RecordId::SourceLocation)?, ), ( "compact_lineage_facts", - registry::class(py, RecordId::CompactLineageFacts)?, + record_class(py, RecordId::CompactLineageFacts)?, ), ( "lineage_column_fact", - registry::class(py, RecordId::CompiledLineageColumnFact)?, + record_class(py, RecordId::CompiledLineageColumnFact)?, ), ( "lineage_source_fact", - registry::class(py, RecordId::CompiledLineageSourceFact)?, + record_class(py, RecordId::CompiledLineageSourceFact)?, ), ( "contract_proof", - registry::class(py, RecordId::DynamicColumnContractProof)?, + record_class(py, RecordId::DynamicColumnContractProof)?, ), ( "family_proof", - registry::class(py, RecordId::DynamicColumnFamilyProof)?, + record_class(py, RecordId::DynamicColumnFamilyProof)?, ), ]; dict( @@ -285,24 +283,21 @@ fn input_view_classes<'py>(py: Python<'py>) -> PyResult> { vec![ ( "sql_reference", - registry::class(py, RecordId::CompileSqlReference)?, + record_class(py, RecordId::CompileSqlReference)?, ), ( "reference_kind", - registry::class(py, RecordId::SqlReferenceKind)?, + record_class(py, RecordId::SqlReferenceKind)?, ), ( "schema_model_entry", - registry::class(py, RecordId::SchemaModelEntry)?, + record_class(py, RecordId::SchemaModelEntry)?, ), ( "sql_expansion", - registry::class(py, RecordId::CompiledSqlExpansion)?, - ), - ( - "expansion_span", - registry::class(py, RecordId::ExpansionSpan)?, + record_class(py, RecordId::CompiledSqlExpansion)?, ), + ("expansion_span", record_class(py, RecordId::ExpansionSpan)?), ( "audits", dict( @@ -327,11 +322,11 @@ fn input_view_classes<'py>(py: Python<'py>) -> PyResult> { vec![ ( "function_argument", - registry::class(py, RecordId::FunctionArgument)?, + record_class(py, RecordId::FunctionArgument)?, ), ( "function_return_column", - registry::class(py, RecordId::FunctionReturnColumn)?, + record_class(py, RecordId::FunctionReturnColumn)?, ), ( "function_language", @@ -384,7 +379,7 @@ pub(crate) fn model_loop_classes<'py>( ("provider_names", provider_names.into_bound_py_any(py)?), ( "python_hook_entry", - registry::class(py, RecordId::PythonHookEntry)?, + record_class(py, RecordId::PythonHookEntry)?, ), ( "signature", @@ -405,7 +400,7 @@ pub(crate) fn model_loop_classes<'py>( dict( py, vec![ - ("usage_record", registry::class(py, RecordId::UsageRecord)?), + ("usage_record", record_class(py, RecordId::UsageRecord)?), ("compile_error", registry::error(py, ErrorId::CompileInput)?), ("syntax_help", host("syntax_help")?), ("source_syntax_help", host("source_syntax_help")?), @@ -415,21 +410,18 @@ pub(crate) fn model_loop_classes<'py>( "scope_path_error", registry::error(py, ErrorId::InvalidScopePath)?, ), - ( - "sql_hook_entry", - registry::class(py, RecordId::SqlHookEntry)?, - ), + ("sql_hook_entry", record_class(py, RecordId::SqlHookEntry)?), ( "audit_factories", by_attribute(audit_factories, "name")?.into_any(), ), ( "generated_usage", - registry::member(py, RecordId::UsageKind, "GENERATED")?, + enum_member(py, RecordId::UsageKind, "GENERATED")?, ), ( "contract_enforced", - registry::member(py, RecordId::ContractPolicy, "ENFORCED")?, + enum_member(py, RecordId::ContractPolicy, "ENFORCED")?, ), ( "model_schemas", @@ -439,14 +431,14 @@ pub(crate) fn model_loop_classes<'py>( ("schema_enums", schema_enums(&resolver)?.into_any()), ( "compiler_diagnostic", - registry::class(py, RecordId::CompilerDiagnostic)?, + record_class(py, RecordId::CompilerDiagnostic)?, ), ("report_diagnostic", host("report_diagnostic")?), ("path_class", imported(py, "pathlib", "Path")?), ("external_resolver", external_resolver.clone()), ( "reference_kind", - registry::class(py, RecordId::SqlReferenceKind)?, + record_class(py, RecordId::SqlReferenceKind)?, ), ( "config", 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 e751dac524..7a237f6c43 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/records.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/records.rs @@ -14,9 +14,9 @@ use crate::bindings::_helpers::compiled_project::model_input_view::NativeModelIn use crate::bindings::_helpers::compiled_project::project::{NativeCompiledProject, model_facts}; use crate::bindings::_helpers::compiled_project::project_view::NativeProjectView; use crate::bindings::_helpers::compiled_project::schema_facts::entry_schema_fact; +use crate::bindings::_helpers::records::construct::{enum_member, new_record, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; const DATACLASSES_MODULE: &str = "dataclasses"; const DECIMAL_KIND: &str = "decimal"; @@ -39,7 +39,7 @@ 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: RecordId) -> PyResult> { let py: Python<'py> = value.py(); - let class: Bound<'py, PyAny> = registry::class(py, class)?; + let class: Bound<'py, PyAny> = record_class(py, class)?; if value.is_instance(&class)? { return Ok(value.clone()); } @@ -111,7 +111,7 @@ pub(crate) fn compiled_project_record<'py>( include_sql_expansions: bool, ) -> PyResult> { let py: Python<'py> = project.py(); - let class: Bound<'py, PyAny> = registry::class(py, RecordId::CompiledProject)?; + let class: Bound<'py, PyAny> = record_class(py, RecordId::CompiledProject)?; if project.is_instance(&class)? { if include_sql_expansions { return Ok(project.clone()); @@ -129,7 +129,7 @@ pub(crate) fn compiled_project_record<'py>( ] { records_of(&fields, pair)?; } - class.call((), Some(&fields)) + new_record(py, RecordId::CompiledProject, (), Some(&fields)) } /// The model with these final binding diagnostics. @@ -140,7 +140,7 @@ pub(crate) fn with_binding_diagnostics<'py>( diagnostics: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = model.py(); - if model.is_instance(®istry::class(py, RecordId::CompiledModel)?)? { + if model.is_instance(&record_class(py, RecordId::CompiledModel)?)? { return replace(model, &[("binding_diagnostics", diagnostics.clone())]); } model.call_method1("with_binding_diagnostics", (diagnostics,)) @@ -163,7 +163,7 @@ pub(crate) fn with_type_recovery<'py>( unchecked.push(column); } } - if !model.is_instance(®istry::class(py, RecordId::CompiledModel)?)? { + if !model.is_instance(&record_class(py, RecordId::CompiledModel)?)? { return model.call_method1("with_type_recovery", (diagnostics, unchecked)); } let inferred: Bound<'py, PyAny> = native_field(model, "inferred_columns")?; @@ -223,7 +223,7 @@ fn with_changes<'py>( ) -> PyResult> { let py: Python<'py> = value.py(); let changes: Bound<'py, PyDict> = changes.unwrap_or_else(|| PyDict::new(py)); - if value.is_instance(®istry::class(py, class)?)? { + if value.is_instance(&record_class(py, class)?)? { return imported(py, DATACLASSES_MODULE, "replace")?.call((value,), Some(&changes)); } value.call_method("with_changes", (), Some(&changes)) @@ -253,7 +253,7 @@ pub(crate) fn compile_inputs_view<'py>( } let keywords: Bound<'py, PyDict> = fields.copy()?; keywords.set_item("native_project", native_project)?; - registry::class(py, RecordId::CompileProjectInputs)?.call((), Some(&keywords)) + new_record(py, RecordId::CompileProjectInputs, (), Some(&keywords)) } /// A typed SQL value as the plain value native consumers read. @@ -391,8 +391,7 @@ pub(crate) fn record_model_analyses<'py>( .map(|model| model.extract::>()) .collect::>()?; let mut transferred: usize = NativeCompiledProject::record_view_analyses(project, views)?; - let nullable: Bound<'py, PyAny> = - registry::class(py, RecordId::InferredNullability)?.getattr("NULLABLE")?; + let nullable: Bound<'py, PyAny> = enum_member(py, RecordId::InferredNullability, "NULLABLE")?; for model in &models { if model.is_instance_of::() { continue; From e56b480ab83bd3653fa6c5da8dba67b923b448ea Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Sat, 17 Oct 2026 01:37:04 +0100 Subject: [PATCH 15/28] =?UTF-8?q?refactor:=20construct=20native=20records?= =?UTF-8?q?=20directly=20in=20contract=5Fcommand=20=E2=80=A6=20cursor=20bi?= =?UTF-8?q?ndings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace per-use sqlbuild._native class lookups with records::construct helpers (record construction, enum member and value lookup). --- .../_helpers/contract_command/command.rs | 11 ++++--- .../_helpers/contract_command/write.rs | 12 ++++--- .../contracts/contract_diagnostics.rs | 13 ++++---- .../_helpers/contracts/project_config.rs | 5 +-- .../_helpers/contracts/spec_resolution.rs | 26 ++++++++------- .../bindings/_helpers/contracts/sql_values.rs | 13 ++++---- .../bindings/_helpers/cost_command/bounds.rs | 4 +-- .../_helpers/cost_report/degradation.rs | 4 +-- .../bindings/_helpers/cost_report/records.rs | 5 +-- .../_helpers/cost_telemetry/allocation.rs | 7 ++-- .../_helpers/cost_telemetry/ledger.rs | 10 ++---- .../_helpers/cost_telemetry/snowflake.rs | 11 +++---- .../bindings/_helpers/cursor/conversion.rs | 19 ++++++----- .../src/bindings/_helpers/cursor/overrides.rs | 4 +-- .../src/bindings/_helpers/cursor/scalars.rs | 33 ++++++++----------- 15 files changed, 92 insertions(+), 85 deletions(-) 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 b6599f754c..8705569b8b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/contract_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/contract_command/command.rs @@ -26,6 +26,7 @@ use crate::bindings::_helpers::compile_driver::python_calls::keywords; 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::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -79,8 +80,9 @@ fn select_resources<'py>( context: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = request.py(); - let analysis: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::CompileAnalysisSelection)?, + let analysis: Bound<'py, PyAny> = new_record_with( + py, + RecordId::CompileAnalysisSelection, &[("no_cache", PyBool::new(py, true).to_owned().into_any())], )?; let project: Bound<'py, PyAny> = build_compiled_project( @@ -405,8 +407,9 @@ fn run_contract_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult< let selection: Selection<'_> = select_resources(request, &discovered, &context)?; let progress: Progress<'_> = progress_for(request)?; let evidence: Bound<'_, PyAny> = compare(request, &context, &selection, &progress)?; - let mut result: Bound<'_, PyAny> = call_with( - ®istry::class(py, RecordId::ContractAdoptionResult)?, + let mut result: Bound<'_, PyAny> = new_record_with( + 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 5bc9f1632c..382fd74e47 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/contract_command/write.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/contract_command/write.rs @@ -8,6 +8,7 @@ use crate::bindings::_helpers::compile_driver::discovery::discover_project_input use crate::bindings::_helpers::compile_driver::project::build_compiled_project; use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::records::construct::{enum_member, new_record}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -33,8 +34,7 @@ pub(crate) fn write_contracts<'py>( let project: Bound<'py, PyAny> = native_field(graph, "project")?; let models: Bound<'py, PyDict> = by_name(&native_field(&project, "models")?)?; let sources: Bound<'py, PyDict> = by_name(&native_field(&project, "sources")?)?; - let model_type: Bound<'py, PyAny> = - registry::class(py, RecordId::CompiledResourceType)?.getattr("MODEL")?; + let model_type: Bound<'py, PyAny> = enum_member(py, RecordId::CompiledResourceType, "MODEL")?; let updates: Bound<'py, PyDict> = PyDict::new(py); let updated_evidence: Bound<'py, PyList> = PyList::empty(py); for evidence in native_field(result, "evidence")?.try_iter()? { @@ -141,8 +141,12 @@ fn applied<'py>( } let selection_keywords: Bound<'py, PyDict> = PyDict::new(py); selection_keywords.set_item("no_cache", true)?; - let selection: Bound<'py, PyAny> = registry::class(py, RecordId::CompileAnalysisSelection)? - .call((), Some(&selection_keywords))?; + let selection: Bound<'py, PyAny> = new_record( + py, + RecordId::CompileAnalysisSelection, + (), + Some(&selection_keywords), + )?; build_compiled_project( py, Some(keywords( 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 0a8084a1e8..190a4f9be6 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/contracts/contract_diagnostics.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/contracts/contract_diagnostics.rs @@ -11,6 +11,7 @@ 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::construct::{enum_from_value, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -65,7 +66,7 @@ fn output_column_location<'py>( }; let py: Python<'py> = model.py(); let location: Bound<'py, PyAny> = keyword_call( - ®istry::class(py, RecordId::SourceLocation)?, + &record_class(py, RecordId::SourceLocation)?, vec![ ("path", native_field(model, "relative_path")?), ("line", line.into_pyobject(py)?.into_any()), @@ -112,7 +113,7 @@ fn related_locations<'py>( return Ok(PyTuple::empty(py)); }; let related: Bound<'py, PyAny> = keyword_call( - ®istry::class(py, RecordId::RelatedLocation)?, + &record_class(py, RecordId::RelatedLocation)?, vec![ ("label", OUTPUT_LABEL.into_pyobject(py)?.into_any()), ("location", location), @@ -130,7 +131,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( - ®istry::class(py, RecordId::CompilerDiagnostic)?, + &record_class(py, RecordId::CompilerDiagnostic)?, vec![ ("phase", CONTRACT_PHASE.into_pyobject(py)?.into_any()), ("severity", severity.into_pyobject(py)?.into_any()), @@ -154,7 +155,7 @@ fn contract_diagnostic<'py>( } fn model_resource_type(py: Python<'_>) -> PyResult> { - registry::class(py, RecordId::CompiledResourceType)?.call1((MODEL_RESOURCE,)) + enum_from_value(py, RecordId::CompiledResourceType, MODEL_RESOURCE) } /// Every model's column contract diagnostics, in model order. @@ -195,7 +196,7 @@ pub(crate) fn evaluate_model_contracts<'py>( } } keyword_call( - ®istry::class(py, RecordId::ContractValidationResult)?, + &record_class(py, RecordId::ContractValidationResult)?, vec![("diagnostics", PyTuple::new(py, diagnostics)?.into_any())], ) } @@ -224,7 +225,7 @@ pub(crate) fn promotion_conflict_diagnostics<'py>( ), &native, )?; - let diagnostic_type: Bound<'py, PyAny> = registry::class(py, RecordId::CompilerDiagnostic)?; + let diagnostic_type: Bound<'py, PyAny> = record_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 9d5236873c..c370bfe172 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/contracts/project_config.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/contracts/project_config.rs @@ -16,6 +16,7 @@ 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::construct::{new_record, record_class}; use crate::bindings::_helpers::records::ids::ErrorId; use crate::bindings::_helpers::records::registry; use sqlbuild_core::constants::PROJECT_CONFIG_FILENAME; @@ -115,7 +116,7 @@ fn enum_member<'py>(py: Python<'py>, class: &str, value: &str) -> PyResult = match PYTHON_ENUM_MODULES.iter().find(|(name, _)| *name == class) { Some((_, module)) => py.import(*module)?.getattr(class)?, - None => registry::class(py, registry::record_named(class)?)?, + None => record_class(py, registry::record_named(class)?)?, }; class_object.call1((value,)) } @@ -129,7 +130,7 @@ fn object<'py>( for (name, value) in fields { keywords.set_item(name, built(py, value)?)?; } - registry::class(py, registry::record_named(class)?)?.call((), Some(&keywords)) + new_record(py, registry::record_named(class)?, (), Some(&keywords)) } fn built(py: Python<'_>, value: Built) -> PyResult> { 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 a262bdf308..5e0cd028fd 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/contracts/spec_resolution.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/contracts/spec_resolution.rs @@ -12,10 +12,10 @@ 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; use crate::bindings::_helpers::contracts::effective_config::{ effective_config_target_fields, effective_config_target_name, }; +use crate::bindings::_helpers::records::construct::{new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -73,12 +73,11 @@ pub(crate) fn resolve_target_config<'py>( local_config: &Bound<'py, PyAny>, target_name: &Bound<'py, PyAny>, ) -> PyResult> { - 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()), target_name, - &target_class.call0()?, + &new_record(py, RecordId::TargetConfig, (), None)?, )?; let Some(rows) = rows else { let message: Bound<'py, PyString> = error.unwrap_or_else(|| PyString::new(py, "")); @@ -100,10 +99,10 @@ pub(crate) fn resolve_target_config<'py>( } } for ((group, class_name), (_, fields)) in TARGET_GROUPS.iter().zip(&grouped) { - let nested: Bound<'py, PyAny> = registry::class(py, *class_name)?.call((), Some(fields))?; + let nested: Bound<'py, PyAny> = new_record(py, *class_name, (), Some(fields))?; values.set_item(*group, nested)?; } - target_class.call((), Some(&values)) + new_record(py, RecordId::TargetConfig, (), Some(&values)) } /// Resolve the effective adapter name, allowing local override. @@ -183,8 +182,9 @@ pub(crate) fn resolve_effective_scenario_config<'py>( )) }) .collect::>()?; - call_with( - ®istry::class(py, RecordId::ScenarioConfig)?, + new_record_with( + py, + RecordId::ScenarioConfig, &[ ( "run_namespace", @@ -196,10 +196,7 @@ pub(crate) fn resolve_effective_scenario_config<'py>( ("local_type_overrides", overrides.into_any()), ( "snapshot_limits", - call_with( - ®istry::class(py, RecordId::ScenarioSnapshotLimitsConfig)?, - &limits, - )?, + new_record_with(py, RecordId::ScenarioSnapshotLimitsConfig, &limits)?, ), ], ) @@ -282,7 +279,12 @@ pub(crate) fn loader_parts<'py>( ), _ => (text(parts[0]), text(parts[1]), text(parts[2])), }; - registry::class(py, RecordId::LoaderDestinationParts)?.call1((database, schema, table)) + new_record( + py, + RecordId::LoaderDestinationParts, + (database, schema, table), + None, + ) } /// 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 e424f1c74e..dc47daa3e4 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,6 @@ //! Typed SQL value normalization between Python authored values and native `SqlValue`s. +use crate::bindings::_helpers::records::construct::{new_record, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -30,11 +31,11 @@ impl<'py> SqlValueClasses<'py> { pub(crate) fn load(py: Python<'py>) -> PyResult { Ok(Self { decimal: py.import("decimal")?.getattr("Decimal")?, - 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)?, + authored_set: record_class(py, RecordId::AuthoredSqlSet)?, + value_call: record_class(py, RecordId::AuthoredSqlValueCall)?, + sql_value: record_class(py, RecordId::SqlValue)?, + logical_type: record_class(py, RecordId::SqlLogicalType)?, + kind: record_class(py, RecordId::SqlValueKind)?, }) } } @@ -199,7 +200,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(), - _ => registry::class(py, RecordId::SqlValueLimits)?.call0()?, + _ => new_record(py, RecordId::SqlValueLimits, (), None)?, }; let limits = SqlValueLimits { max_depth: native_field(&applied, "max_depth")?.extract()?, 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 86b1ce0cac..1ce23b0463 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,9 @@ 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::construct::record_class; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; const DATETIME_MODULE: &str = "datetime"; const MINUTE_TO_DAY_DURATION_UNITS: [&str; 3] = ["m", "h", "d"]; @@ -103,7 +103,7 @@ pub(crate) fn parse_since<'py>(py: Python<'py>, value: &str) -> PyResult = - registry::class(py, RecordId::Duration)?.call_method1("parse", (value,))?; + record_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_report/degradation.rs b/crates/sqlbuild-python/src/bindings/_helpers/cost_report/degradation.rs index cd5c5e490b..a78f31296b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_report/degradation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_report/degradation.rs @@ -17,9 +17,9 @@ 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::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; const LOGGER_NAME: &str = "sqlbuild.cli.commands._helpers.cost.collection"; const COLLECTION_LOGGER_NAME: &str = "sqlbuild.cost"; @@ -164,7 +164,7 @@ fn cost_record<'py>( "had_executable_work", native_field(finalization, "had_executable_work")?, )); - call_with(®istry::class(py, RecordId::CostRunRecord)?, &fields) + new_record_with(py, RecordId::CostRunRecord, &fields) } fn summary<'py>( 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 f1358d228e..b759e9dfb3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_report/records.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_report/records.rs @@ -8,6 +8,7 @@ use sqlbuild_core::cost_report::models::{CostResource, CostRun, CostSummary}; 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::records::construct::new_record; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -155,7 +156,7 @@ fn resource_object<'py>( builders.decimal(&resource.estimated_compute_credits)?, )?; fields.set_item("estimated_usd", builders.decimal(&resource.estimated_usd)?)?; - registry::class(py, RecordId::ResourceCost)?.call((), Some(&fields)) + new_record(py, RecordId::ResourceCost, (), Some(&fields)) } fn summary_object<'py>( @@ -216,7 +217,7 @@ pub(crate) fn record_object<'py>(py: Python<'py>, run: &CostRun) -> PyResult( resource: &ResourceAllocation, ) -> PyResult> { let decimal = |value: &PyDecimal| python_decimal(py, value); - call_with( - ®istry::class(py, RecordId::ResourceCost)?, + new_record_with( + py, + RecordId::ResourceCost, &[ ( "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 45d4cd2a1a..ccbd1af6bc 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/ledger.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/ledger.rs @@ -15,12 +15,11 @@ use sqlbuild_cache::digest::main::sha256_hex::sha256_hex; use sqlbuild_core::json::main::dumps::dumps; use sqlbuild_core::json::models::{JsonDialect, JsonInteger, JsonValue, StdlibJsonOptions}; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, within, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported, within}; use crate::bindings::_helpers::cost_telemetry::health::mark_ledger_failure; use crate::bindings::_helpers::cost_telemetry::values::process_object; use crate::bindings::_helpers::cursor::values::DATETIME_MODULE; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -200,10 +199,7 @@ fn ledger_entry<'py>(line: &Bound<'py, PyAny>) -> PyResult> { ("started_at", parsed("started_at")?), ("completed_at", parsed("completed_at")?), ]; - call_with( - ®istry::class(py, RecordId::StatementLedgerEntry)?, - &entry_fields, - ) + new_record_with(py, RecordId::StatementLedgerEntry, &entry_fields) } fn entry_error(py: Python<'_>, error: &PyErr) -> bool { 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 c08efd5777..66e5165d36 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/snowflake.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/snowflake.rs @@ -13,18 +13,16 @@ use sqlbuild_core::cost_allocation::constants::QUERY_HISTORY_RESULT_LIMIT; use sqlbuild_core::cost_allocation::main::query_history_sql::query_history_sql; use sqlbuild_core::cost_allocation::models::{QueryHistoryWindow, RunAllocation}; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported}; use crate::bindings::_helpers::cost_telemetry::allocation::{ SummaryState, allocate_observations, priced, summary_object, }; 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::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; const DEADLINE_SECONDS: f64 = 10.0; const QUERY_TIMEOUT_SECONDS: &str = "5"; @@ -259,8 +257,9 @@ fn row_observation<'py>( return Ok(Err(NO_WAREHOUSE_COMPUTE)); } let tag: QueryTag = tag.unwrap_or_default(); - let observation: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::QueryCostObservation)?, + let observation: Bound<'py, PyAny> = new_record_with( + 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/cursor/conversion.rs b/crates/sqlbuild-python/src/bindings/_helpers/cursor/conversion.rs index 51349afb9f..a67df9d222 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cursor/conversion.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cursor/conversion.rs @@ -20,9 +20,11 @@ use crate::bindings::_helpers::cursor::values::{ }; use crate::bindings::_helpers::native_host::facade::FacadeHost; use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::construct::{ + enum_from_value, new_record_with, record_class, +}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; /// Cursor grain names, finest first. pub(crate) const GRAIN_NAMES: [(&str, Grain); 6] = [ @@ -59,9 +61,9 @@ impl<'py> CursorSession<'py> { pub(crate) fn new(py: Python<'py>) -> PyResult { Ok(Self { py, - timestamp_class: registry::class(py, RecordId::TimestampValue)?, - date_class: registry::class(py, RecordId::DateValue)?, - integer_class: registry::class(py, RecordId::IntegerValue)?, + timestamp_class: record_class(py, RecordId::TimestampValue)?, + date_class: record_class(py, RecordId::DateValue)?, + integer_class: record_class(py, RecordId::IntegerValue)?, inputs: RefCell::new(Vec::new()), failure: RefCell::new(None), }) @@ -135,7 +137,7 @@ impl<'py> CursorSession<'py> { return Ok(Some(*known)); } } - registry::class(self.py, RecordId::CursorGrain)?.call1((grain,))?; + enum_from_value(self.py, RecordId::CursorGrain, grain)?; Err(PyTypeError::new_err("cursor grain must be a CursorGrain")) } @@ -198,13 +200,14 @@ impl<'py> CursorSession<'py> { .iter() .find(|(_, known)| *known == grain) .map_or("", |(name, _)| name); - registry::class(self.py, RecordId::CursorGrain)?.call1((name,)) + enum_from_value(self.py, RecordId::CursorGrain, name) } /// A new `AlignedInterval`, validated as its constructor validates. pub(crate) fn python_interval(&self, interval: &Interval) -> PyResult> { - call_with( - ®istry::class(self.py, RecordId::AlignedInterval)?, + new_record_with( + 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/overrides.rs b/crates/sqlbuild-python/src/bindings/_helpers/cursor/overrides.rs index d3178e8364..92b95542c1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cursor/overrides.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cursor/overrides.rs @@ -8,16 +8,16 @@ 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::algebra_error; use crate::bindings::_helpers::cursor::values::DATETIME_MODULE; +use crate::bindings::_helpers::records::construct::record_class; use crate::bindings::_helpers::records::fields::field as native_field; 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: RecordId) -> PyResult { - value.is_instance(®istry::class(value.py(), class)?) + value.is_instance(&record_class(value.py(), class)?) } /// A timestamp override as a `datetime`; dates start at midnight and text is ISO 8601. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cursor/scalars.rs b/crates/sqlbuild-python/src/bindings/_helpers/cursor/scalars.rs index d3e9377179..8595a0fa79 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cursor/scalars.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cursor/scalars.rs @@ -24,14 +24,14 @@ use sqlbuild_cursor::algebra::main::parse_integer_text::parse_integer_text; 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::compile_driver::python_calls::imported; 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::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; const DECIMAL_MODULE: &str = "decimal"; const INTEGER_CURSOR: &str = "integer"; @@ -49,9 +49,12 @@ 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> = registry::class(py, RecordId::IntegerValue)?; if let Some(value) = parse_integer_text(&text) { - return call_with(&class, &[("value", value.into_pyobject(py)?.into_any())]); + return new_record_with( + py, + RecordId::IntegerValue, + &[("value", value.into_pyobject(py)?.into_any())], + ); } let parsed: PyResult<(Bound<'py, PyAny>, Bound<'py, PyAny>)> = imported(py, DECIMAL_MODULE, "Decimal")? @@ -78,7 +81,7 @@ fn parse_integer<'py>(raw: &Bound<'py, PyAny>) -> PyResult> { &format!("non-integral integer cursor value: {text}"), )); } - call_with(&class, &[("value", integer)]) + new_record_with(py, RecordId::IntegerValue, &[("value", integer)]) } /// Whether `int(Decimal(...))` failed with `InvalidOperation`, `ValueError` or `OverflowError`. @@ -108,10 +111,7 @@ fn parse_timestamp_text<'py>(raw: &Bound<'py, PyAny>, text: &str) -> PyResult { - return call_with( - ®istry::class(py, RecordId::DateValue)?, - &[("value", date)], - ); + return new_record_with(py, RecordId::DateValue, &[("value", date)]); } Err(error) if error.is_instance_of::(py) => {} Err(error) => return Err(error), @@ -120,8 +120,9 @@ fn parse_timestamp_text<'py>(raw: &Bound<'py, PyAny>, text: &str) -> PyResult call_with( - ®istry::class(py, RecordId::TimestampValue)?, + Ok(value) => new_record_with( + py, + RecordId::TimestampValue, &[ ("value", value), ("source_text", PyString::new(py, &normalized).into_any()), @@ -153,16 +154,10 @@ 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( - ®istry::class(py, RecordId::TimestampValue)?, - &[("value", value)], - ); + return new_record_with(py, RecordId::TimestampValue, &[("value", value)]); } if raw.is_instance(&imported(py, DATETIME_MODULE, "date")?)? { - return call_with( - ®istry::class(py, RecordId::DateValue)?, - &[("value", raw.clone())], - ); + return new_record_with(py, RecordId::DateValue, &[("value", raw.clone())]); } let Ok(text) = raw.downcast::() else { return Err(algebra_error( From bee315b640943bc9b6c05960678a53fb5a4d8fc9 Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Sat, 17 Oct 2026 01:37:04 +0100 Subject: [PATCH 16/28] =?UTF-8?q?refactor:=20construct=20native=20records?= =?UTF-8?q?=20directly=20in=20dag=5Fartifact=20=E2=80=A6=20discovery=20bin?= =?UTF-8?q?dings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace per-use sqlbuild._native class lookups with records::construct helpers (record construction, enum member and value lookup). --- .../_helpers/dbt_command/auto_init.rs | 11 ++-- .../bindings/_helpers/dbt_command/command.rs | 11 ++-- .../src/bindings/_helpers/dbt_command/init.rs | 11 ++-- .../_helpers/dbt_command/init_project.rs | 6 +- .../bindings/_helpers/dbt_events/results.rs | 14 ++--- .../src/bindings/_helpers/dbt_interop/cli.rs | 18 +++--- .../bindings/_helpers/dbt_interop/graph.rs | 6 +- .../bindings/_helpers/dbt_interop/manifest.rs | 25 ++++---- .../_helpers/dbt_interop/orchestration.rs | 3 +- .../bindings/_helpers/dbt_interop/pipeline.rs | 15 +++-- .../_helpers/dbt_interop/pipeline_phases.rs | 23 +++++--- .../_helpers/dbt_interop/pipeline_prologue.rs | 21 ++++--- .../src/bindings/_helpers/dbt_interop/plan.rs | 14 +++-- .../_helpers/dbt_interop/plan_output.rs | 28 +++++---- .../bindings/_helpers/dbt_interop/profile.rs | 9 +-- .../_helpers/dbt_interop/profile_load.rs | 7 ++- .../dbt_interop/reference_resolver.rs | 6 +- .../bindings/_helpers/dbt_interop/runner.rs | 11 ++-- .../_helpers/dbt_interop/runtime_config.rs | 16 +++-- .../_helpers/dbt_interop/runtime_execution.rs | 12 ++-- .../_helpers/dbt_interop/runtime_results.rs | 12 ++-- .../_helpers/dbt_interop/sqlbuild_work.rs | 8 ++- .../_helpers/debug_command/run_debug.rs | 3 +- .../bindings/_helpers/diagnostics/trackers.rs | 4 +- .../bindings/_helpers/diff_report/query.rs | 5 +- .../_helpers/discovery/configuration.rs | 7 ++- .../_helpers/discovery/declaration_records.rs | 59 +++++++++++-------- .../_helpers/discovery/description_inputs.rs | 12 ++-- .../src/bindings/_helpers/discovery/faults.rs | 8 +-- .../_helpers/discovery/header_declarations.rs | 46 +++++++++------ .../_helpers/discovery/header_values.rs | 7 ++- .../_helpers/discovery/hook_entries.rs | 6 +- .../_helpers/discovery/imported_modules.rs | 3 +- .../discovery/imported_python_nodes.rs | 12 ++-- .../_helpers/discovery/model_file_record.rs | 22 ++++--- .../discovery/python_function_files.rs | 7 ++- .../_helpers/discovery/scope_snapshot.rs | 22 +++---- .../_helpers/discovery/sql_test_blocks.rs | 30 ++++++---- .../_helpers/discovery/sql_test_records.rs | 14 +++-- .../_helpers/discovery/yaml_records.rs | 16 ++--- 40 files changed, 335 insertions(+), 235 deletions(-) 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 3710ca73c8..aa6f2d337e 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 @@ -13,6 +13,7 @@ use sqlbuild_core::dbt_command::models::ForwardedArg; use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::dbt_command::support::{color_enabled, flag, path, stream}; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; @@ -87,8 +88,9 @@ pub(crate) fn ensure_twin_project<'py>( ("use_color", flag(py, use_color)), ], )?; - let callbacks: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::DbtInitProgressCallbacks)?, + let callbacks: Bound<'py, PyAny> = new_record_with( + py, + RecordId::DbtInitProgressCallbacks, &[ ("start", native_field(&progress, "start")?), ("complete", native_field(&progress, "complete")?), @@ -103,8 +105,9 @@ pub(crate) fn ensure_twin_project<'py>( Some(raw) => PyString::new(py, &raw).into_any(), None => none.clone(), }; - let request: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::DbtInitRequest)?, + let request: Bound<'py, PyAny> = new_record_with( + py, + RecordId::DbtInitRequest, &[ ("cwd", effective_dir), ("dbt_project_dir", dbt_project_dir), 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 3a35b84ba0..a55df3d778 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/command.rs @@ -11,6 +11,7 @@ use crate::bindings::_helpers::dbt_command::auto_init::{args_tuple, ensure_twin_ 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::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -79,8 +80,9 @@ fn run_plan(project_dir: &Bound<'_, PyAny>, args: &[String], no_color: bool) -> ("use_color", flag(py, progress.use_color)), ], )?; - let display: Bound<'_, PyAny> = call_with( - ®istry::class(py, RecordId::DisplayOptions)?, + let display: Bound<'_, PyAny> = new_record_with( + py, + RecordId::DisplayOptions, &[( "max_entries_per_section", if options.verbose { @@ -115,8 +117,9 @@ fn run_execution( let options: DbtOutputOptions = dbt_output_options(args); let progress: Progress<'_> = Progress::new(py, &options, no_color)?; progress.blank_line()?; - let request: Bound<'_, PyAny> = call_with( - ®istry::class(py, RecordId::DbtInteropExecutionRequest)?, + let request: Bound<'_, PyAny> = new_record_with( + py, + RecordId::DbtInteropExecutionRequest, &[ ("command", command.clone()), ("project_dir", project_dir.clone()), 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 6b2c6b68a7..63844e5a4f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/init.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/init.rs @@ -13,6 +13,7 @@ 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}; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -87,15 +88,17 @@ fn run_dbt_init_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult< ("use_color", flag(py, use_color)), ], )?; - let callbacks: Bound<'_, PyAny> = call_with( - ®istry::class(py, RecordId::DbtInitProgressCallbacks)?, + let callbacks: Bound<'_, PyAny> = new_record_with( + py, + RecordId::DbtInitProgressCallbacks, &[ ("start", native_field(&progress, "start")?), ("complete", native_field(&progress, "complete")?), ], )?; - let init_request: Bound<'_, PyAny> = call_with( - ®istry::class(py, RecordId::DbtInitRequest)?, + let init_request: Bound<'_, PyAny> = new_record_with( + py, + RecordId::DbtInitRequest, &[ ("cwd", native_field(request, "cwd")?), ("dbt_project_dir", project_dir), 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 fdb10dcd25..e020a4268f 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 @@ -17,6 +17,7 @@ use crate::bindings::_helpers::dbt_interop::profile_load::{ load_dbt_project_metadata, load_selected_dbt_profile_output, }; use crate::bindings::_helpers::dbt_interop::runner::launch_os_error; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::types::CompilerDetach; use sqlbuild_config::dbt_profile::models::DbtProjectMetadata; @@ -146,8 +147,9 @@ fn build_dbt_init_project<'py>(request: &Bound<'py, PyAny>) -> PyResult>( py: Python<'py>, @@ -32,13 +31,13 @@ pub(crate) fn python_result<'py>( py: Python<'py>, result: &DbtNodeResult, ) -> PyResult> { - let message_class: Bound<'py, PyAny> = registry::class(py, RecordId::DbtNodeMessage)?; let messages: Vec> = result .messages .iter() .map(|message| { - call_with( - &message_class, + new_record_with( + py, + RecordId::DbtNodeMessage, &[ ("level", text(py, &message.level)), ("message", text(py, &message.message)), @@ -46,8 +45,9 @@ pub(crate) fn python_result<'py>( ) }) .collect::>()?; - call_with( - ®istry::class(py, RecordId::DbtNodeExecutionResult)?, + new_record_with( + 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 d38960d760..f4e6cf8cae 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/cli.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/cli.rs @@ -21,6 +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::construct::{ + enum_from_value, new_record_with, record_class, +}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -224,7 +227,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> = registry::class(py, RecordId::DbtLsNode)?; + let node_class: Bound<'py, PyAny> = record_class(py, RecordId::DbtLsNode)?; let mut nodes: Vec> = Vec::new(); for line in python_splitlines(stdout) { let stripped: &str = python_strip(line); @@ -299,10 +302,10 @@ fn route_dbt_interop_args<'py>( let flags: DbtParsedFlags = parsed_flags(parsed)?; let (kind, routed): (DbtInteropCommandKind, DbtRoutedArgs) = route(&command, &flags).map_err(|error| raised(py, ErrorId::DbtInteropArgument, error))?; - let command: Bound<'py, PyAny> = - registry::class(py, RecordId::DbtInteropCommand)?.call1((kind.name(),))?; - call_with( - ®istry::class(py, RecordId::DbtInteropRoutedArgs)?, + let command: Bound<'py, PyAny> = enum_from_value(py, RecordId::DbtInteropCommand, kind.name())?; + new_record_with( + py, + RecordId::DbtInteropRoutedArgs, &[ ("command", command), ("select", PyTuple::new(py, flags.select)?.into_any()), @@ -382,8 +385,9 @@ fn resolve_dbt_config<'py>( || py.None().into_bound(py), |target| PyString::new(py, &target).into_any(), ); - call_with( - ®istry::class(py, RecordId::ResolvedDbtConfig)?, + new_record_with( + py, + RecordId::ResolvedDbtConfig, &[ ( "project_dir", 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 c7cf0daf34..6c3666957c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/graph.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/graph.rs @@ -21,6 +21,7 @@ use sqlbuild_core::dbt_graph::types::DbtSelectorResolver; 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::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -267,8 +268,9 @@ fn selection_result<'py>(py: Python<'py>, selection: DbtSelection) -> PyResult>()?; - call_with( - ®istry::class(py, RecordId::DbtInteropSelectionResult)?, + new_record_with( + 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 cea8e6a43b..ec1074ad70 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/manifest.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/manifest.rs @@ -19,9 +19,8 @@ use sqlbuild_config::dbt_manifest::models::{ ManifestNode, NodeChecksum, ParsedModel, ParsedSeed, ParsedSource, }; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported}; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -139,8 +138,9 @@ fn model_object<'py>( raw: &Bound<'py, PyDict>, ) -> PyResult> { let py: Python<'py> = raw.py(); - call_with( - ®istry::class(py, RecordId::DbtManifestModel)?, + new_record_with( + py, + RecordId::DbtManifestModel, &[ ("unique_id", unique_id.clone()), ("package_name", text(py, &model.package_name)), @@ -170,8 +170,9 @@ fn seed_object<'py>( raw: &Bound<'py, PyDict>, ) -> PyResult> { let py: Python<'py> = raw.py(); - call_with( - ®istry::class(py, RecordId::DbtManifestSeed)?, + new_record_with( + py, + RecordId::DbtManifestSeed, &[ ("unique_id", unique_id.clone()), ("package_name", text(py, &seed.package_name)), @@ -191,8 +192,9 @@ fn source_object<'py>( raw: &Bound<'py, PyDict>, ) -> PyResult> { let py: Python<'py> = raw.py(); - call_with( - ®istry::class(py, RecordId::DbtManifestSource)?, + new_record_with( + py, + RecordId::DbtManifestSource, &[ ("unique_id", unique_id.clone()), ("package_name", text(py, &source.package_name)), @@ -337,8 +339,9 @@ fn manifest_index<'py>(raw_data: &Bound<'py, PyAny>) -> PyResult( let arguments: KeywordArguments<'py> = KeywordArguments::read(py, "plan_dbt_interop_command", options, PLAN_OPTIONS)?; let command: Bound<'py, PyAny> = - registry::class(py, RecordId::DbtInteropCommand)?.call1((arguments.value("command")?,))?; + enum_from_value(py, RecordId::DbtInteropCommand, arguments.value("command")?)?; let command_args: Bound<'py, PyAny> = arguments.value("command_args")?; let (select, exclude) = ( native_field(&command_args, "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 5a66b8c059..453b46f13d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/pipeline.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/pipeline.rs @@ -14,6 +14,7 @@ use crate::bindings::_helpers::compile_driver::python_calls::keywords; 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::construct::{enum_member, new_record_with, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -108,7 +109,7 @@ fn resolve_plan<'py>( fn execute_dbt_interop_from_project(request: &Bound<'_, PyAny>) -> PyResult { let py = request.py(); let command = native_field(request, "command")?; - let kind = registry::class(py, RecordId::DbtInteropCommand)?; + let kind = record_class(py, RecordId::DbtInteropCommand)?; if !command.eq(native_field(&kind, "RUN")?)? && !command.eq(native_field(&kind, "BUILD")?)? { return Err(PyErr::from_value( registry::error(py, ErrorId::DbtInteropArgument)?.call1((format!( @@ -245,12 +246,13 @@ fn plan_dbt_interop_from_project<'py>( options: Option>, ) -> PyResult> { let args = KeywordArguments::read(py, "plan_dbt_interop_from_project", options, PLAN_OPTIONS)?; - let request = call_with( - ®istry::class(py, RecordId::DbtInteropExecutionRequest)?, + let request = new_record_with( + py, + RecordId::DbtInteropExecutionRequest, &[ ( "command", - registry::class(py, RecordId::DbtInteropCommand)?.getattr("PLAN")?, + enum_member(py, RecordId::DbtInteropCommand, "PLAN")?, ), ("project_dir", args.value("project_dir")?), ("args", args.value("args")?), @@ -354,8 +356,9 @@ fn debug_dbt_from_project(py: Python<'_>, options: Option>) -> ("require_project_dir", boolean(py, true)), ], )?; - let dbt_options = call_with( - ®istry::class(py, RecordId::DbtCliOptions)?, + let dbt_options = new_record_with( + py, + RecordId::DbtCliOptions, &[ ("project_dir", native_field(&resolved, "project_dir")?), ("profiles_dir", native_field(&resolved, "profiles_dir")?), 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 4bf18bb67a..e892ea14b2 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,6 +10,7 @@ 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::construct::{new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -32,8 +33,9 @@ const OPTIONS: (&[&str], &[&str]) = ( fn environment<'py>(args: &KeywordArguments<'py>) -> PyResult> { let request = args.value("request")?; let compiled = args.value("compiled")?; - call_with( - ®istry::class(request.py(), RecordId::DbtPlanEnvironment)?, + new_record_with( + request.py(), + RecordId::DbtPlanEnvironment, &[ ("project_dir", native_field(&request, "project_dir")?), ( @@ -99,8 +101,9 @@ fn write_dbt_execution_plan_text( ("use_color", native_field(&request, "use_color")?), ( "display_options", - call_with( - ®istry::class(py, RecordId::DisplayOptions)?, + new_record_with( + py, + RecordId::DisplayOptions, &[("max_entries_per_section", limit)], )?, ), @@ -206,8 +209,9 @@ fn resolve_sqlbuild_execution_plan_output<'py>( ("use_color", args.value("request")?.getattr("use_color")?), ], )?; - let request = call_with( - ®istry::class(py, RecordId::DbtSqlbuildPlanRequest)?, + let request = new_record_with( + py, + RecordId::DbtSqlbuildPlanRequest, &[ ("selected_model_names", selected), ( @@ -236,7 +240,7 @@ fn execution_limits<'py>( ) -> PyResult> { let py = discovered.py(); if target.is_none() { - return registry::class(py, RecordId::ExecutionLimitsConfig)?.call0(); + return new_record(py, RecordId::ExecutionLimitsConfig, (), None); } call_with( ®istry::native(py, NativeAttr::ResolveTargetConfig)?, @@ -265,8 +269,9 @@ fn run_dbt_sqlbuild_work<'py>( let compiled = args.value("compiled")?; let project = native_field(&compiled, "project")?; let discovered = native_field(&invocation, "discovered_inputs")?; - let context = call_with( - ®istry::class(py, RecordId::DbtSqlbuildWorkContext)?, + let context = new_record_with( + py, + RecordId::DbtSqlbuildWorkContext, &[ ("plan_output", args.value("plan_output")?), ("connection_config", args.value("connection_config")?), 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 207cf71e76..4d27733dc7 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 @@ -4,6 +4,7 @@ use crate::bindings::_helpers::compile_driver::discovery::discover_project_input use crate::bindings::_helpers::compile_driver::project::build_compiled_project; use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::records::construct::new_record_with; use pyo3::prelude::{ Bound, PyAny, PyAnyMethods, PyErr, PyModule, PyModuleMethods, PyResult, Python, }; @@ -105,8 +106,9 @@ pub(crate) fn resolve_dbt_execution_invocation<'py>( )?; } let dbt_stream = native_field(request, "dbt_stdout_stream")?; - call_with( - ®istry::class(py, RecordId::DbtInteropInvocation)?, + new_record_with( + py, + RecordId::DbtInteropInvocation, &[ ("dbt_executable", executable), ("output_stream", stream.clone()), @@ -211,8 +213,9 @@ fn compile_dbt_interop_project<'py>( )?), )?; completed(&callback, "Compiled SQLBuild project.", start)?; - call_with( - ®istry::class(py, RecordId::DbtInteropCompiledProject)?, + new_record_with( + py, + RecordId::DbtInteropCompiledProject, &[ ("adapter_name", name), ("adapter", adapter), @@ -246,8 +249,9 @@ fn resolve_dbt_interop_plan<'py>( let invocation = args.value("invocation")?; let routed = native_field(&invocation, "routed")?; let tuple = py.import("builtins")?.getattr("tuple")?; - let command_args = call_with( - ®istry::class(py, RecordId::DbtInteropCommandArgs)?, + let command_args = new_record_with( + py, + RecordId::DbtInteropCommandArgs, &[ ("select", tuple.call1((native_field(&routed, "select")?,))?), ( @@ -282,8 +286,9 @@ fn resolve_dbt_interop_plan<'py>( ], )?; completed(&callback, "Resolved dbt and SQLBuild selection.", start)?; - call_with( - ®istry::class(py, RecordId::DbtInteropPlanResolution)?, + new_record_with( + 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 07d12ad906..86a8b1f23b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/plan.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/plan.rs @@ -9,6 +9,9 @@ use sqlbuild_core::cli_text::main::format_dbt_interop_plan_json::format_dbt_inte use sqlbuild_core::cli_text::models::{DbtInteropPlanText, DbtLsNodeText, DisplayLimit}; use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::records::construct::{ + enum_from_value, enum_member, new_record, new_record_with, +}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -121,7 +124,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> = - registry::class(py, RecordId::DbtInteropCommand)?.call1((arguments.value("command")?,))?; + enum_from_value(py, RecordId::DbtInteropCommand, 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()? { @@ -149,8 +152,9 @@ pub(crate) fn build_dbt_interop_plan<'py>( strings(&empty_or(arguments.value("warnings")?))?, dbt_has_work || sqlbuild_has_work, ); - call_with( - ®istry::class(py, RecordId::DbtInteropPlan)?, + new_record_with( + py, + RecordId::DbtInteropPlan, &[ ("command", command), ( @@ -212,7 +216,7 @@ fn render_dbt_interop_plan(py: Python<'_>, options: Option>) - } let mut display: Bound<'_, PyAny> = arguments.value("display_options")?; if !display.is_truthy()? { - display = registry::class(py, RecordId::DisplayOptions)?.call0()?; + display = new_record(py, RecordId::DisplayOptions, (), None)?; } let use_color: bool = arguments.flag("use_color")?; let plan_output: Bound<'_, PyAny> = native_field(&plan, "sqlbuild_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 7af4501e51..a750afd466 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 @@ -14,6 +14,7 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ }; use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::construct::{new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -161,8 +162,9 @@ pub(crate) fn build_sqlbuild_plan_output<'py>( ("defer_to", flag_value(&args, "--defer-to")?), ], )?; - let cursor = call_with( - ®istry::class(py, RecordId::CursorOverrides)?, + let cursor = new_record_with( + py, + RecordId::CursorOverrides, &[ ("start_ts", flag_value(&args, "--start-cursor-ts")?), ("end_ts", flag_value(&args, "--end-cursor-ts")?), @@ -171,12 +173,14 @@ pub(crate) fn build_sqlbuild_plan_output<'py>( ], )?; let full_refresh = boolean(py, args.contains("--full-refresh")?); - let selection = call_with( - ®istry::class(py, RecordId::PlannerSelection)?, + let selection = new_record_with( + py, + RecordId::PlannerSelection, &[("select", selected.clone())], )?; - let overrides = call_with( - ®istry::class(py, RecordId::PlannerOverrides)?, + let overrides = new_record_with( + py, + RecordId::PlannerOverrides, &[ ("cursor_overrides", cursor), ("full_refresh", full_refresh.clone()), @@ -198,7 +202,7 @@ pub(crate) fn build_sqlbuild_plan_output<'py>( ("deferral", deferral), ( "policies", - registry::class(py, RecordId::PlannerPolicies)?.call0()?, + new_record(py, RecordId::PlannerPolicies, (), None)?, ), ("on_progress", native_field(&hooks, "on_progress")?), ( @@ -238,8 +242,9 @@ fn attach_sqlbuild_plan_output<'py>( let args = KeywordArguments::read(py, "attach_sqlbuild_plan_output", options, ATTACH_OPTIONS)?; let compiled = args.value("compiled")?; let plan = args.value("plan")?; - let environment = call_with( - ®istry::class(py, RecordId::DbtPlanEnvironment)?, + let environment = new_record_with( + py, + RecordId::DbtPlanEnvironment, &[ ("project_dir", args.value("project_dir")?), ("discovered_inputs", args.value("discovered_inputs")?), @@ -248,8 +253,9 @@ fn attach_sqlbuild_plan_output<'py>( ("adapter_name", native_field(&compiled, "adapter_name")?), ], )?; - let request = call_with( - ®istry::class(py, RecordId::DbtSqlbuildPlanRequest)?, + let request = new_record_with( + 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 1bb253c083..1db6527af0 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/profile.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/profile.rs @@ -12,12 +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}; +use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::native_host::third_party::ThirdPartyHost; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; const INIT_TOML_OPTIONS: (&[&str], &[&str]) = ( &[ @@ -81,8 +81,9 @@ fn normalized_connection<'py>( }; connection.set_item(key, value)?; } - call_with( - ®istry::class(py, RecordId::NormalizedDbtProfileConnection)?, + new_record_with( + 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_load.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/profile_load.rs index 60090e0373..fd8e338baa 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 @@ -15,8 +15,8 @@ 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::construct::new_record_with; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; const DBT_PROJECT_FILE: &str = "dbt_project.yml"; const PROFILES_FILE: &str = "profiles.yml"; @@ -131,8 +131,9 @@ pub(crate) fn load_selected_dbt_profile_output<'py>( .map_err(|error| raised(py, error))?; let (target, index): (String, usize) = select_dbt_profile_target(&validated, target_name).map_err(|error| raised(py, error))?; - call_with( - ®istry::class(py, RecordId::SelectedDbtProfileOutput)?, + new_record_with( + 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 f965dcd379..5b9db65f90 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 @@ -12,6 +12,7 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ }; use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -257,8 +258,9 @@ impl DbtCompileReferenceResolver { ) -> Result<(), PyErr> { let arguments: KeywordArguments<'_> = KeywordArguments::read(py, "validate_reference", options, VALIDATE_OPTIONS)?; - let reference: Bound<'_, PyAny> = call_with( - ®istry::class(py, RecordId::CompileSqlReference)?, + let reference: Bound<'_, PyAny> = new_record_with( + py, + RecordId::CompileSqlReference, &[ ("ref_kind", arguments.value("ref_kind")?), ("ref_name", arguments.value("ref_name")?), 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 d620f39fcc..4e5549bcb6 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/runner.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/runner.rs @@ -22,6 +22,7 @@ use crate::bindings::_helpers::dbt_interop::cli::{ }; use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -125,8 +126,9 @@ impl DbtRunner { let result: Bound<'py, PyAny> = self.run(command, &native_field(options, "project_dir")?)?; let stdout: String = native_field(&result, "stdout")?.extract()?; - let listing: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::DbtLsResult)?, + let listing: Bound<'py, PyAny> = new_record_with( + py, + RecordId::DbtLsResult, &[ ("nodes", ls_nodes(py, &stdout)?.into_any()), ("command", result), @@ -193,8 +195,9 @@ impl DbtRunner { Ok(output) }); let output: DbtProcessOutput = output.map_err(|error| launch_failure(py, error))?; - call_with( - ®istry::class(py, RecordId::DbtCommandResult)?, + new_record_with( + py, + RecordId::DbtCommandResult, &[ ("argv", command.into_any()), ("returncode", output.returncode.into_pyobject_any(py)?), 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 50cb28f9f8..fe2dc9dda0 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 @@ -16,6 +16,7 @@ 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::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -104,8 +105,9 @@ pub(crate) fn parsed_flags( } fn parsed_args_model<'py>(py: Python<'py>, flags: DbtParsedFlags) -> PyResult> { - call_with( - ®istry::class(py, RecordId::DbtInteropParsedArgs)?, + new_record_with( + py, + RecordId::DbtInteropParsedArgs, &[ ("select", PyTuple::new(py, flags.select)?.into_any()), ("exclude", PyTuple::new(py, flags.exclude)?.into_any()), @@ -184,8 +186,9 @@ pub(crate) fn config_overrides<'py>( args: &[String], ) -> PyResult> { let value = |name: &str| optional_text(py, dbt_flag_value(args, name)); - call_with( - ®istry::class(py, RecordId::DbtCliConfigOverrides)?, + new_record_with( + py, + RecordId::DbtCliConfigOverrides, &[ ("project_dir", value("--project-dir")), ("profiles_dir", value("--profiles-dir")), @@ -284,8 +287,9 @@ pub(crate) fn plan_options<'py>( &[("obj", values.into_any()), ("sort_keys", flag(py, true))], )? }; - call_with( - ®istry::class(py, RecordId::DbtCliOptions)?, + new_record_with( + py, + RecordId::DbtCliOptions, &[ ("project_dir", native_field(&resolved, "project_dir")?), ("profiles_dir", native_field(&resolved, "profiles_dir")?), 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 cc27de688d..61e37ed30b 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 @@ -2,6 +2,7 @@ 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::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -112,7 +113,6 @@ fn execute_dbt_commands<'py>( ®istry::native(py, NativeAttr::CliStyle)?, &[("use_color", arguments.value("use_color")?)], )?; - let result_type = registry::class(py, RecordId::DbtCommandExecutionResult)?; if argv.is_none() { let message = arguments.value("skip_message")?; let message = if message.is_none() { @@ -129,8 +129,9 @@ fn execute_dbt_commands<'py>( .extract::()? ), )?; - return call_with( - &result_type, + return new_record_with( + py, + RecordId::DbtCommandExecutionResult, &[("returncode", PyInt::new(py, 0).into_any())], ); } @@ -240,8 +241,9 @@ fn execute_dbt_commands<'py>( } } progress(&arguments, "Finalized dbt run.")?; - call_with( - &result_type, + new_record_with( + py, + RecordId::DbtCommandExecutionResult, &[("returncode", returncode), ("node_results", results)], ) } 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 75fdc61f63..64f90fa032 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,9 +1,9 @@ //! Backfill streamed dbt results using Python's JSON and UTF-8 artifact contract. use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; 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::{ @@ -48,8 +48,9 @@ fn messages<'py>(raw: &Bound<'py, PyDict>) -> PyResult> { }; PyTuple::new( py, - [call_with( - ®istry::class(py, RecordId::DbtNodeMessage)?, + [new_record_with( + py, + RecordId::DbtNodeMessage, &[ ("level", PyString::new(py, "error").into_any()), ("message", text), @@ -128,8 +129,9 @@ pub(crate) fn append_missing<'py>( } else { py.None().into_bound(py) }; - appended.push(call_with( - ®istry::class(py, RecordId::DbtNodeExecutionResult)?, + appended.push(new_record_with( + 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 8c25401c45..cf5cd7eb26 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 @@ -14,6 +14,7 @@ use crate::bindings::_helpers::build_command::support::{ 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::construct::{new_record_with, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -33,8 +34,9 @@ const WORK_OPTIONS: (&[&str], &[&str]) = ( /// 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( - ®istry::class(py, RecordId::PlanSafetyGates)?, + new_record_with( + py, + RecordId::PlanSafetyGates, &[ ("allow_missing_migration_origin", boolean(py, false)), ("allow_snapshot_full_refresh", boolean(py, false)), @@ -110,7 +112,7 @@ fn execute_dbt_sqlbuild_work<'py>( ], )?; let command = args.value("command")?; - let kinds = registry::class(py, RecordId::DbtInteropCommand)?; + let kinds = record_class(py, RecordId::DbtInteropCommand)?; let display = if command.eq(native_field(&kinds, "RUN")?)? { "sqb build --no-tests --no-audits".to_owned() } else { 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 c5c6fb0c02..c38203bc8b 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 @@ -36,6 +36,7 @@ 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::construct::record_class; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -123,7 +124,7 @@ fn warehouse_checks( selected_target: &context.selected_target, }; let mut checks: Vec = Vec::new(); - for group in registry::class(py, RecordId::WarehouseGroup)?.try_iter()? { + for group in record_class(py, RecordId::WarehouseGroup)?.try_iter()? { let group: Bound<'_, PyAny> = group?; let group_name: String = native_field(&group, "value")?.str()?.extract()?; if !supports_session_warehouse { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/diagnostics/trackers.rs b/crates/sqlbuild-python/src/bindings/_helpers/diagnostics/trackers.rs index 4dab1204b6..51334aaf6e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/diagnostics/trackers.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/diagnostics/trackers.rs @@ -10,8 +10,8 @@ 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::construct::new_record; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; const TRACKER_VARIABLE: &str = "sqlbuild_build_phase_timing_tracker"; const PHASES: [&str; 6] = [ @@ -107,7 +107,7 @@ impl BuildPhaseTimingTracker { keywords.set_item(phase, value)?; } keywords.set_item("total_seconds", non_negative(now, self.started_at))?; - registry::class(py, RecordId::PartialBuildPhaseTimings)?.call((), Some(&keywords)) + new_record(py, RecordId::PartialBuildPhaseTimings, (), Some(&keywords)) } /// Return the active build timing tracker. 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 71d42879e0..1e1f8cd1cd 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/diff_report/query.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/diff_report/query.rs @@ -22,6 +22,7 @@ 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::construct::record_class; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -258,7 +259,7 @@ pub(crate) fn prepare_query_diff<'py>( ) }; call( - ®istry::class(py, RecordId::QueryDiffPreparation)?, + &record_class(py, RecordId::QueryDiffPreparation)?, &[ ("adapter", &adapter), ("connection_config", &config), @@ -698,7 +699,7 @@ fn inspect_inputs<'py>( )); } } - let ttl: Bound<'py, PyAny> = registry::class(py, RecordId::Duration)? + let ttl: Bound<'py, PyAny> = record_class(py, RecordId::Duration)? .call_method1("parse", (native_field(preparation, "artifact_ttl")?,))?; if ttl.is_none() { return Err(failure( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/configuration.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/configuration.rs index 3b6b868ec7..b7ff43f7eb 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/configuration.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/configuration.rs @@ -9,8 +9,8 @@ use crate::bindings::_helpers::contracts::project_config::{ }; use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; /// `DiscoveredProjectInputs` with the loaded configuration, inside the project discovery lifecycle. pub(crate) fn discover_project_configuration<'py>( @@ -28,8 +28,9 @@ pub(crate) fn discover_project_configuration<'py>( ], )?; within(&lifecycle, |_| { - call_with( - ®istry::class(py, RecordId::DiscoveredProjectInputs)?, + new_record_with( + py, + RecordId::DiscoveredProjectInputs, &[ ("project_dir", project_dir.clone()), ( 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 3f427818eb..06e03d02e1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/declaration_records.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/declaration_records.rs @@ -16,7 +16,7 @@ use sqlbuild_discovery::tree::models::ProjectTree; use sqlbuild_sqltext::compiler::models::AuthoredValue; use std::sync::Arc; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; +use crate::bindings::_helpers::compile_driver::python_calls::{imported, keywords}; use crate::bindings::_helpers::discovery::constants; use crate::bindings::_helpers::discovery::faults::{FileFaults, record_tuple}; use crate::bindings::_helpers::discovery::header_declarations::typed_constant_record; @@ -29,8 +29,8 @@ use crate::bindings::_helpers::discovery::sorted_keys::sorted_keys; use crate::bindings::_helpers::model_config::header_columns::schema_columns; use crate::bindings::_helpers::native_host::facade::FacadeHost; use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::construct::{enum_from_value, new_record, new_record_with}; 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}; @@ -72,7 +72,7 @@ impl<'py> FileContext<'_, 'py> { } fn record(&self, class: RecordId, fields: &Bound<'py, PyDict>) -> PyResult> { - registry::class(self.py(), class)?.call((), Some(fields)) + new_record(self.py(), class, (), Some(fields)) } } @@ -85,7 +85,7 @@ pub(crate) fn add_scope_fields( let py: Python<'_> = fields.py(); fields.set_item( "scope_kind", - registry::class(py, RecordId::ScopeKind)?.call1((scope.scope_kind,))?, + enum_from_value(py, RecordId::ScopeKind, scope.scope_kind)?, )?; for (name, path) in [ ("ownership_root", &scope.ownership_root), @@ -128,16 +128,21 @@ 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> = 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)? .iter() - .map(|(name, value)| call_with(&member_class, &[("name", name), ("value", value)])) + .map(|(name, value)| { + new_record_with( + py, + RecordId::EnumMember, + &[("name", name), ("value", value)], + ) + }) .collect::>()?; - declarations.push(call_with( - &declaration_class, + declarations.push(new_record_with( + py, + RecordId::EnumDeclaration, &[ ("name", declaration.name.as_str().into_bound_py_any(py)?), ("members", PyTuple::new(py, members)?.into_any()), @@ -189,13 +194,13 @@ pub(crate) fn source_locations<'py>( relative_path: &Bound<'py, PyAny>, rows: &[(String, LineColumnSpan)], ) -> PyResult> { - 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( name, - call_with( - &class, + new_record_with( + py, + RecordId::SourceLocation, &[ ("path", relative_path.clone()), ("line", span.line.into_bound_py_any(py)?), @@ -215,8 +220,6 @@ fn schema_file<'py>( ) -> PyResult> { let py: Python<'py> = context.py(); let file_path: String = context.file_path.str()?.to_string(); - let declaration_class: Bound<'py, PyAny> = - 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> = @@ -230,8 +233,9 @@ fn schema_file<'py>( &source_locations(py, &context.relative_path, &declaration.column_locations)?, )? .into_any(); - declarations.push(call_with( - &declaration_class, + declarations.push(new_record_with( + py, + RecordId::ModelSchemaDeclaration, &[ ("name", declaration.name.as_str().into_bound_py_any(py)?), ( @@ -288,8 +292,9 @@ fn audit_block<'py>(py: Python<'py>, block: &AuditBlock) -> PyResult = if mode.is(&host::entry(py, FacadeHost::AuditEvaluationMode)?.getattr(MEASUREMENT_MODE)?) { - call_with( - ®istry::class(py, RecordId::MeasurementContract)?, + new_record_with( + py, + RecordId::MeasurementContract, &[ ("value_column", header_values.as_any().get_item("value")?), ( @@ -309,8 +314,9 @@ fn audit_block<'py>(py: Python<'py>, block: &AuditBlock) -> PyResult( let declaration_kind: &str = context.scope.map_or("", |scope| scope.declaration_kind); fields.set_item( "declaration_kind", - registry::class(py, RecordId::DeclarationKind)?.call1((declaration_kind,))?, + enum_from_value(py, RecordId::DeclarationKind, declaration_kind)?, )?; context.add_scope(&fields)?; context.record(RecordId::DiscoveredAuditFile, &fields) @@ -518,13 +524,13 @@ fn seed_records<'py, T>( files: &[ScopedFile], ) -> PyResult> { let py: Python<'py> = faults.project_dir.py(); - let class: Bound<'py, PyAny> = registry::class(py, RecordId::DiscoveredSeedFile)?; let seeds: Vec> = files .iter() .map(|scoped| { let relative: Py = path_object(py, tree, scoped.file.relative_path.clone())?; - call_with( - &class, + new_record_with( + py, + RecordId::DiscoveredSeedFile, &[ ("file_path", faults.project_dir.div(relative.bind(py))?), ("relative_path", path_class(py)?.call1((relative,))?), @@ -665,8 +671,9 @@ pub(crate) fn discover_adapter_file<'py>( if !file_path.call_method0("is_file")?.is_truthy()? { return Ok(py.None().into_bound(py)); } - call_with( - ®istry::class(py, RecordId::DiscoveredAdapterFile)?, + new_record_with( + 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 dc659ce42d..eaf8f24422 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/description_inputs.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/description_inputs.rs @@ -1,10 +1,9 @@ //! Model description resolution for header-only tooling, without importing project Python. -use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; +use pyo3::prelude::{Bound, PyAny, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyCFunction, PyDict, PyFrozenSet}; use pyo3::{pyfunction, wrap_pyfunction}; -use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::contracts::project_config::load_project_config_file; use crate::bindings::_helpers::discovery::declaration_records::discover_model_schema_files; use crate::bindings::_helpers::discovery::model_records::{ @@ -12,8 +11,8 @@ use crate::bindings::_helpers::discovery::model_records::{ }; use crate::bindings::_helpers::discovery::snapshot::in_snapshot; use crate::bindings::_helpers::model_config::described_models::externally_described_models as described_models; +use crate::bindings::_helpers::records::construct::{new_record, new_record_with}; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::render::declaration_indexes::public_model_schema_index; /// Models and model schemas, skipping files that cannot be read. @@ -31,13 +30,14 @@ pub(crate) fn discover_model_description_inputs<'py>( })? .into_any(); in_snapshot(project_dir, || { - call_with( - ®istry::class(py, RecordId::DiscoveredProjectInputs)?, + new_record_with( + py, + RecordId::DiscoveredProjectInputs, &[ ("project_config", project_config), ( "local_config", - registry::class(py, RecordId::LocalConfig)?.call0()?, + new_record(py, RecordId::LocalConfig, (), None)?, ), ( "model_files", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/faults.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/faults.rs index dcecb736de..62ab716dc5 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/faults.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/faults.rs @@ -4,9 +4,8 @@ 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; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; const PROJECT_DIR_MARKER: &str = "."; @@ -45,8 +44,9 @@ impl<'py> FileFaults<'py> { .value(py) .str()? .call_method1("replace", (self.project_dir.str()?, PROJECT_DIR_MARKER))?; - on_fault.call1((call_with( - ®istry::class(py, RecordId::DiscoveryFileFault)?, + on_fault.call1((new_record_with( + 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 d8223255b9..6db76378e9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/header_declarations.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/header_declarations.rs @@ -18,6 +18,7 @@ use sqlbuild_model_config::sql_values::models::RawSqlValue; use crate::bindings::_helpers::contracts::sql_values::{ SqlValueClasses, raw_value, sql_value, sql_value_error, }; +use crate::bindings::_helpers::records::construct::{enum_from_value, new_record}; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -51,7 +52,6 @@ fn enum_record<'py>( facts: EnumFacts, owner: &Owner<'py>, ) -> PyResult> { - let member_class = registry::class(py, RecordId::EnumMember)?; let int_class = py.import("builtins")?.getattr("int")?; let members = facts .members @@ -61,16 +61,21 @@ fn enum_record<'py>( EnumValue::Text(text) => text.into_bound_py_any(py)?, EnumValue::Integer(digits) => int_class.call1((digits,))?, }; - member_class.call1((name, value)) + new_record(py, RecordId::EnumMember, (name, value), None) }) .collect::>>()?; - registry::class(py, RecordId::EnumDeclaration)?.call1(( - facts.name, - PyTuple::new(py, members)?, - facts.scalar_type, - &owner.relative_path, - owner.model_name.as_deref(), - )) + new_record( + py, + RecordId::EnumDeclaration, + ( + facts.name, + PyTuple::new(py, members)?, + facts.scalar_type, + &owner.relative_path, + owner.model_name.as_deref(), + ), + None, + ) } fn constant_record<'py>( @@ -80,18 +85,21 @@ fn constant_record<'py>( classes: &SqlValueClasses<'py>, ) -> PyResult> { let render_as = match facts.render_as { - Some(rendering) => { - registry::class(py, RecordId::CollectionRendering)?.call1((rendering,))? - } + Some(rendering) => enum_from_value(py, RecordId::CollectionRendering, rendering)?, None => py.None().into_bound(py), }; - registry::class(py, RecordId::ConstantDeclaration)?.call1(( - facts.name, - sql_value(&facts.value, classes)?, - &owner.relative_path, - owner.model_name.as_deref(), - render_as, - )) + new_record( + py, + RecordId::ConstantDeclaration, + ( + facts.name, + sql_value(&facts.value, classes)?, + &owner.relative_path, + owner.model_name.as_deref(), + render_as, + ), + None, + ) } 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 ff016dd6db..602b483475 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/header_values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/header_values.rs @@ -6,6 +6,7 @@ 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::construct::new_record; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::sqltext::model_headers::{ @@ -101,7 +102,6 @@ fn source_locations<'py>( relative_path: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = relative_path.py(); - let location_class: Bound<'py, PyAny> = registry::class(py, RecordId::SourceLocation)?; let locations: Bound<'py, PyDict> = PyDict::new(py); for (name, line, column, end_line, end_column) in rows { let arguments: Bound<'py, PyDict> = PyDict::new(py); @@ -110,7 +110,10 @@ fn source_locations<'py>( arguments.set_item("column", column)?; arguments.set_item("end_line", end_line)?; arguments.set_item("end_column", end_column)?; - locations.set_item(name, location_class.call((), Some(&arguments))?)?; + locations.set_item( + name, + new_record(py, RecordId::SourceLocation, (), Some(&arguments))?, + )?; } Ok(locations) } 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 db3314521e..bf4698cb96 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::records::construct::record_class; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; const RELATIVE_PATH_FIELD: &str = "relative_path"; const SQL_HOOK_FIELDS: [&str; 6] = [ @@ -48,8 +48,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> = registry::class(py, RecordId::SqlHookEntry)?; - let python_class: Bound<'py, PyAny> = registry::class(py, RecordId::PythonHookEntry)?; + let sql_class: Bound<'py, PyAny> = record_class(py, RecordId::SqlHookEntry)?; + let python_class: Bound<'py, PyAny> = record_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 ba8affd828..400c8c0c51 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/imported_modules.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/imported_modules.rs @@ -10,6 +10,7 @@ use crate::bindings::_helpers::discovery::python_module_files::discover_python_m use crate::bindings::_helpers::event_exporting::scope::current_scope; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -105,7 +106,7 @@ pub(crate) fn discovered<'py>( class_name: RecordId, pairs: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - call_with(®istry::class(py, class_name)?, pairs) + new_record_with(py, class_name, pairs) } /// `sqlbuild._native.(message)`. 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 22de979c8b..2fb552b3ee 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 @@ -12,6 +12,7 @@ use crate::bindings::_helpers::discovery::imported_modules::{ module_functions, provider_by_name, provider_usages, shim, tuple_of, }; use crate::bindings::_helpers::discovery::imported_providers; +use crate::bindings::_helpers::records::construct::new_record; use crate::bindings::_helpers::discovery::python_hook_files::discover_python_hook_files; use crate::bindings::_helpers::graph::python_node_graph::node_identity; @@ -19,7 +20,6 @@ use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::native_host::user_code::UserCodeHost; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; -use crate::bindings::_helpers::records::registry; use pyo3::IntoPyObjectExt; use sqlbuild_core::constants::PYTHON_NODE_ROOT; const NODE_ERROR: ErrorId = ErrorId::PythonNodeDiscovery; @@ -503,10 +503,12 @@ pub(crate) fn discover_hook_functions<'py>( ] { arguments.set_item(field, value)?; } - hooks.push( - registry::class(py, RecordId::DiscoveredHookFunction)? - .call((), Some(&arguments))?, - ); + hooks.push(new_record( + py, + RecordId::DiscoveredHookFunction, + (), + Some(&arguments), + )?); } } tuple_of(py, hooks) 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 cfaaca1bb7..5f2b073cee 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,7 +1,7 @@ //! A discovered SQL model file as a native record whose Python attributes are built on demand. +use crate::bindings::_helpers::records::construct::new_record; 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; @@ -62,16 +62,20 @@ impl Locations { Ok(self .projected .get_or_try_init(py, || { - 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(( - path.bind(py), - span.line, - span.column, - span.end_line, - span.end_column, - ))?; + let location: Bound<'py, PyAny> = new_record( + py, + RecordId::SourceLocation, + ( + path.bind(py), + span.line, + span.column, + span.end_line, + span.end_column, + ), + None, + )?; dict.set_item(name, location)?; } Ok::<_, pyo3::PyErr>(dict.unbind()) 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 0e218d3d75..b28b116fa7 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,11 +17,11 @@ 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; 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::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -250,8 +250,9 @@ fn function_record<'py>( contents: PyString::new(py, &contents), }; let (header_values, entry_point, body_python) = parsed_function(&source)?; - call_with( - ®istry::class(py, RecordId::DiscoveredPythonFunctionFile)?, + new_record_with( + 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/scope_snapshot.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/scope_snapshot.rs index 63f9f7ee77..c73a2bfbae 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,6 @@ 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; use crate::bindings::_helpers::contracts::project_config::{ load_local_config_file, load_project_config_file, }; @@ -23,10 +22,10 @@ use crate::bindings::_helpers::discovery::sql_test_records::{ discover_scenario_files, discover_test_files, }; use crate::bindings::_helpers::discovery::yaml_records::discover_source_files; +use crate::bindings::_helpers::records::construct::{new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; 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"; @@ -66,8 +65,9 @@ impl<'py> Faults<'py> { Some(path) => path_class(py)?.call1((path,))?, None => py.None().into_bound(py), }; - call_with( - ®istry::class(py, RecordId::DiscoveryFileFault)?, + new_record_with( + py, + RecordId::DiscoveryFileFault, &[ ("path", path), ("message", PyString::new(py, &message).into_any()), @@ -120,8 +120,9 @@ fn configs<'py>( } else { PyString::new(py, FALLBACK_PROJECT_NAME).into_any() }; - call_with( - ®istry::class(py, RecordId::ProjectConfig)?, + new_record_with( + py, + RecordId::ProjectConfig, &[ ("name", name), ("adapter", PyString::new(py, FALLBACK_ADAPTER).into_any()), @@ -133,7 +134,7 @@ fn configs<'py>( faults, (&list, Some(LOCAL_CONFIG_FILENAME)), load_local_config_file(py, project_dir).map(|config| config.into_bound(py)), - || registry::class(py, RecordId::LocalConfig)?.call0(), + || new_record(py, RecordId::LocalConfig, (), None), )?; Ok((project_config, local_config, list)) } @@ -228,9 +229,10 @@ fn tolerant_discovery<'py>(project_dir: &Bound<'py, PyAny>) -> PyResult = - registry::class(py, RecordId::DiscoveredProjectInputs)?.call((), Some(&inputs))?; - call_with( - ®istry::class(py, RecordId::TolerantScopeDiscovery)?, + new_record(py, RecordId::DiscoveredProjectInputs, (), Some(&inputs))?; + new_record_with( + 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 0c7ff037c9..7b28d86fc3 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 @@ -11,6 +11,7 @@ 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::native_host::facade::FacadeHost; use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::construct::{enum_from_value, new_record}; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -44,11 +45,12 @@ fn case_record<'py>( [name.into_bound_py_any(py)?, sql_value(value, classes)?], )?); } - registry::class(py, RecordId::DiscoveredSqlTestCase)?.call1(( - case.name.as_str(), - PyTuple::new(py, values)?, - case_index, - )) + new_record( + py, + RecordId::DiscoveredSqlTestCase, + (case.name.as_str(), PyTuple::new(py, values)?, case_index), + None, + ) } fn block_record<'py>( @@ -57,16 +59,20 @@ fn block_record<'py>( block: SqlTestBlock, classes: &SqlValueClasses<'py>, ) -> PyResult> { - let kind = registry::class(py, RecordId::SqlValueKind)?; let parameters = block .parameters .iter() .map(|parameter| { - registry::class(py, RecordId::SqlTestParameterDeclaration)?.call1(( - parameter.name.as_str(), - kind.call1((parameter.value_type,))?, - parameter.nullable, - )) + new_record( + py, + RecordId::SqlTestParameterDeclaration, + ( + parameter.name.as_str(), + enum_from_value(py, RecordId::SqlValueKind, parameter.value_type)?, + parameter.nullable, + ), + None, + ) }) .collect::>>()?; let cases = block @@ -89,7 +95,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)?; - registry::class(py, RecordId::DiscoveredSqlTestBlock)?.call((), Some(&keywords)) + new_record(py, RecordId::DiscoveredSqlTestBlock, (), 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 bc691b6d4f..7fcbca43fa 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 @@ -15,7 +15,7 @@ use sqlbuild_discovery::sql_tests::models::{ }; use sqlbuild_discovery::tree::models::ProjectTree; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::discovery::constants; use crate::bindings::_helpers::discovery::faults::{FileFaults, record_tuple}; use crate::bindings::_helpers::discovery::project_files::path_object; @@ -27,9 +27,9 @@ 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::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; 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; @@ -128,8 +128,9 @@ fn test_file<'py>( ) -> PyResult> { let (file_path, relative_path) = paths; let py: Python<'py> = file_path.py(); - call_with( - ®istry::class(py, RecordId::DiscoveredSqlTestFile)?, + new_record_with( + py, + RecordId::DiscoveredSqlTestFile, &[ ("file_path", file_path.clone()), ("relative_path", relative_path.clone()), @@ -181,8 +182,9 @@ fn scenario_file<'py>( "SQL scenario '{shown}' must define SQL after SCENARIO(...)" )); } - call_with( - ®istry::class(py, RecordId::DiscoveredSqlScenarioFile)?, + new_record_with( + py, + RecordId::DiscoveredSqlScenarioFile, &[ ("file_path", file_path.clone()), ("relative_path", relative_path.clone()), 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 a70c58da43..6349270505 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/yaml_records.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/yaml_records.rs @@ -17,7 +17,7 @@ use sqlbuild_discovery::yaml_files::models::{YamlFileOutcome, YamlPathFailure}; use std::path::PathBuf; use crate::bindings::_helpers::boundary::config_values::config_value_to_python; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::discovery::faults::{FileFaults, record_tuple}; use crate::bindings::_helpers::discovery::project_files::path_object; use crate::bindings::_helpers::discovery::raised::{ @@ -29,8 +29,8 @@ use crate::bindings::_helpers::discovery::snapshot::{ use crate::bindings::_helpers::discovery::yaml_entries::{ parse_schema_document, parse_sources_document, }; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use crate::bindings::models::{NativeProjectTree, YamlDiscoveryRequest}; /// The most YAML bytes one native load reads before its files are built and released. @@ -223,7 +223,6 @@ pub(crate) fn discover_source_files<'py>( project_dir: project_dir.clone(), on_fault, }; - let class: Bound<'py, PyAny> = registry::class(py, RecordId::DiscoveredSourceFile)?; let mut records: Vec> = Vec::new(); load_files( project_dir, @@ -231,8 +230,9 @@ pub(crate) fn discover_source_files<'py>( |(relative_path, file_path), built| { let record = built.and_then(|(contents, loaded)| { let shown: String = file_path.str()?.to_str()?.to_owned(); - call_with( - &class, + new_record_with( + py, + RecordId::DiscoveredSourceFile, &[ ("file_path", file_path.clone()), ("relative_path", relative_path.clone()), @@ -283,7 +283,6 @@ pub(crate) fn discover_schema_files<'py>( if paths.is_empty() { return record_tuple(py, Vec::new()); } - let class: Bound<'py, PyAny> = registry::class(py, RecordId::DiscoveredSchemaFile)?; let mut records: Vec> = Vec::new(); load_files( project_dir, @@ -291,8 +290,9 @@ pub(crate) fn discover_schema_files<'py>( |(relative_path, file_path), built| { let (contents, loaded) = built?; let shown: String = file_path.str()?.to_str()?.to_owned(); - records.push(call_with( - &class, + records.push(new_record_with( + py, + RecordId::DiscoveredSchemaFile, &[ ("file_path", file_path.clone()), ("relative_path", relative_path), From c892388de05c171f060ada181bd01339c51829bf Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Sat, 17 Oct 2026 01:37:05 +0100 Subject: [PATCH 17/28] =?UTF-8?q?refactor:=20construct=20native=20records?= =?UTF-8?q?=20directly=20in=20event=5Fexporting=20=E2=80=A6=20load=20bindi?= =?UTF-8?q?ngs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace per-use sqlbuild._native class lookups with records::construct helpers (record construction, enum member and value lookup). --- .../_helpers/event_exporting/values.rs | 4 +- .../_helpers/fingerprints/latest_rows.rs | 5 +- .../_helpers/fingerprints/storage_io.rs | 4 +- .../_helpers/format_command/config.rs | 6 ++- .../_helpers/format_command/selection.rs | 9 ++-- .../_helpers/freshness/freshness_command.rs | 4 +- .../_helpers/freshness/observation.rs | 52 +++++++++++-------- .../bindings/_helpers/freshness/planning.rs | 28 +++++----- .../_helpers/freshness/publication.rs | 7 +-- .../bindings/_helpers/freshness/state_io.rs | 17 +++--- .../_helpers/frontier/compile_frontier.rs | 4 +- .../_helpers/graph/execution_gates.rs | 23 ++++---- .../src/bindings/_helpers/graph/keys.rs | 6 +-- .../_helpers/graph/python_lifecycle.rs | 9 +++- .../_helpers/graph/python_node_graph.rs | 22 ++++---- .../src/bindings/_helpers/graph/views.rs | 4 +- .../_helpers/hand_built/compiled_model.rs | 31 ++++++----- .../_helpers/janitor/janitor_command.rs | 5 +- .../_helpers/janitor/janitor_old_names.rs | 6 +-- .../_helpers/lineage/lineage_command.rs | 3 +- .../_helpers/lineage/lineage_index.rs | 6 +-- .../_helpers/lineage/lineage_objects.rs | 30 +++++------ .../bindings/_helpers/load/load_command.rs | 3 +- .../bindings/_helpers/load/load_selection.rs | 6 ++- 24 files changed, 169 insertions(+), 125 deletions(-) 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 8b99a62e64..80ee687a90 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/values.rs @@ -14,8 +14,8 @@ use sqlbuild_core::lifecycle_events::main::catalog::catalog; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::construct::enum_from_value; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; /// `BoundEventExporter` parameters, in positional order. const BOUND_EXPORTER_PARAMETERS: [&str; 5] = [ @@ -52,7 +52,7 @@ pub(crate) fn severity_object( py: Python<'_>, severity: ExportSeverity, ) -> PyResult> { - registry::class(py, RecordId::EventExportSeverity)?.call1((severity.value(),)) + enum_from_value(py, RecordId::EventExportSeverity, severity.value()) } /// An `EventExportSeverity` or its string value, as `EventExportSeverity(value)` accepts it. 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 3987efa485..407544c18f 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,7 @@ use std::ffi::CString; +use crate::bindings::_helpers::records::construct::{new_record, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -37,7 +38,7 @@ pub(crate) fn fingerprint_set_from_rows<'py>( qualified_name: &str, rows: &Bound<'py, PyAny>, ) -> PyResult> { - let fingerprint_class: Bound<'py, PyAny> = registry::class(py, RecordId::Fingerprint)?; + let fingerprint_class: Bound<'py, PyAny> = record_class(py, RecordId::Fingerprint)?; let from_iso: Bound<'py, PyAny> = py .import(DATETIME_MODULE)? .getattr("datetime")? @@ -109,7 +110,7 @@ fn indexed_set<'py>( kwargs.set_item("schema", schema)?; kwargs.set_item("fingerprints", by_name)?; kwargs.set_item("fingerprints_by_identity", by_identity)?; - registry::class(py, RecordId::FingerprintSet)?.call((), Some(&kwargs)) + new_record(py, RecordId::FingerprintSet, (), Some(&kwargs)) } fn identity_of(fingerprint: &Bound<'_, PyAny>) -> PyResult<(String, String)> { 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 f722831ae8..d2beed1e6a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/fingerprints/storage_io.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/fingerprints/storage_io.rs @@ -19,12 +19,12 @@ use crate::bindings::_helpers::fingerprints::latest_rows::{ use crate::bindings::_helpers::fingerprints::storage_sql::{ fingerprint_insert_batches, fingerprint_insert_sql, }; +use crate::bindings::_helpers::records::construct::new_record; use crate::bindings::_helpers::native_host::adapter::AdapterHost; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use sqlbuild_core::constants::FINGERPRINT_TABLE_NAME; const TIME_MODULE: &str = "time"; const LOGGING_MODULE: &str = "logging"; @@ -272,7 +272,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))?; - registry::class(py, RecordId::FingerprintSet)?.call((), Some(&keywords)) + new_record(py, RecordId::FingerprintSet, (), Some(&keywords)) } /// `FingerprintInputError` for an unreadable table, caused by the warehouse error. 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 a2b573c76b..4627f308ac 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/format_command/config.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/format_command/config.rs @@ -10,6 +10,7 @@ use sqlbuild_rules::lint_text::main::resolve_format_settings::resolve_format_set use sqlbuild_rules::lint_text::models::FormatSettings; use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -50,8 +51,9 @@ pub(crate) fn lint_config<'py>( dialect: &str, ) -> PyResult> { let settings: &FormatSettings = &config.settings; - call_with( - ®istry::class(py, RecordId::LintConfig)?, + new_record_with( + py, + RecordId::LintConfig, &[ ( "line_width", 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 631ccf7ebc..a207114618 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/format_command/selection.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/format_command/selection.rs @@ -18,6 +18,7 @@ use crate::bindings::_helpers::command_runtime::adapters::resolve_cli_adapter; use crate::bindings::_helpers::compile_driver::discovery::discover_project_inputs; use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, within}; +use crate::bindings::_helpers::records::construct::{enum_member, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -171,8 +172,7 @@ fn model_paths( ("exclude", selectors(py, exclude)?), ], )?; - let model_type: Bound<'_, PyAny> = - registry::class(py, RecordId::CompiledResourceType)?.getattr("MODEL")?; + let model_type: Bound<'_, PyAny> = enum_member(py, RecordId::CompiledResourceType, "MODEL")?; let mut names: BTreeSet = BTreeSet::new(); for key in keys.try_iter()? { let key: Bound<'_, PyAny> = key?; @@ -257,8 +257,9 @@ fn selected_contract_inputs<'py>( let project_only = |name: NativeAttr| -> PyResult> { call_with(&native(name)?, &[("project_dir", project_dir.clone())]) }; - call_with( - ®istry::class(py, RecordId::DiscoveredProjectInputs)?, + new_record_with( + py, + RecordId::DiscoveredProjectInputs, &[ ("project_config", project_config), ("local_config", local_config), 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 3a493440b1..d5a7a1d512 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/freshness/freshness_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/freshness/freshness_command.rs @@ -27,6 +27,7 @@ use crate::bindings::_helpers::planner_selection::selectors::{ }; use crate::bindings::_helpers::planner_selection::unit_tests::reject_unit_test_selectors; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::construct::enum_member; use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::result_output::documents::write_execution_json_output; @@ -80,8 +81,7 @@ fn source_names_for_keys( ) -> PyResult> { let mut names: BTreeSet = BTreeSet::new(); let py: Python<'_> = graph.py(); - let source_type: Bound<'_, PyAny> = - registry::class(py, RecordId::CompiledResourceType)?.getattr("SOURCE")?; + let source_type: Bound<'_, PyAny> = enum_member(py, RecordId::CompiledResourceType, "SOURCE")?; let upstream_deps: Bound<'_, PyAny> = native_field(graph, "upstream_deps")?; for key in keys.try_iter()? { let key: Bound<'_, PyAny> = key?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/freshness/observation.rs b/crates/sqlbuild-python/src/bindings/_helpers/freshness/observation.rs index d287a8768b..1a0c3751c1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/freshness/observation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/freshness/observation.rs @@ -15,9 +15,11 @@ 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::construct::{ + enum_from_value, enum_member, new_record, new_record_with, +}; use crate::bindings::_helpers::records::fields::field as native_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}; @@ -67,8 +69,9 @@ pub(crate) fn observation_source<'py>( { return Ok(None); } - let config: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::SourceFreshnessConfig)?, + let config: Bound<'py, PyAny> = new_record_with( + py, + RecordId::SourceFreshnessConfig, &[("strategy", adapter_strategy(py)?)], )?; replaced_source(source, config).map(Some) @@ -84,7 +87,7 @@ fn replaced_source<'py>( } fn adapter_strategy(py: Python<'_>) -> PyResult> { - registry::class(py, RecordId::SourceFreshnessStrategy)?.getattr("ADAPTER") + enum_member(py, RecordId::SourceFreshnessStrategy, "ADAPTER") } /// `OperationLifecycle(operation_kind="freshness", ...)` for one observation phase. @@ -122,8 +125,9 @@ fn observation<'py>( (strategy, value_kind): (Bound<'py, PyAny>, Bound<'py, PyAny>), (data_version, observed_at): (Bound<'py, PyAny>, Bound<'py, PyAny>), ) -> PyResult> { - call_with( - ®istry::class(source_name.py(), RecordId::SourceFreshnessObservation)?, + new_record_with( + source_name.py(), + RecordId::SourceFreshnessObservation, &[ ("source_name", source_name.clone()), ("strategy", strategy), @@ -310,13 +314,14 @@ pub(crate) fn unknown<'py>( (reason, message): (&str, Bound<'py, PyAny>), ) -> PyResult> { let py: Python<'py> = source_name.py(); - call_with( - ®istry::class(py, RecordId::SourceFreshnessUnknown)?, + new_record_with( + py, + RecordId::SourceFreshnessUnknown, &[ ("source_name", source_name.clone()), ( "reason", - registry::class(py, RecordId::SourceFreshnessUnknownReason)?.getattr(reason)?, + enum_member(py, RecordId::SourceFreshnessUnknownReason, reason)?, ), ("message", message), ], @@ -364,8 +369,11 @@ fn metadata_observation<'py>( source_name, ( adapter_strategy(py)?, - registry::class(py, RecordId::SourceFreshnessValueKind)? - .call1((native_field(metadata, "value_kind")?,))?, + enum_from_value( + py, + RecordId::SourceFreshnessValueKind, + native_field(metadata, "value_kind")?, + )?, ), ( presumed_utc_value(&native_field(metadata, "data_version")?)?, @@ -409,10 +417,8 @@ pub(crate) fn observe_adapter_batch<'py>( sources: &[Bound<'py, PyAny>], ) -> PyResult> { let py: Python<'py> = observer.adapter.py(); - let batch_class: Bound<'py, PyAny> = - registry::class(py, RecordId::AdapterSourceFreshnessBatch)?; if sources.is_empty() { - return batch_class.call0(); + return new_record(py, RecordId::AdapterSourceFreshnessBatch, (), None); } let manager: Bound<'py, PyAny> = lifecycle( py, @@ -437,8 +443,6 @@ fn observe_adapter_batch_inner<'py>( sources: &[Bound<'py, PyAny>], ) -> PyResult> { let py: Python<'py> = observer.adapter.py(); - let batch_class: Bound<'py, PyAny> = - 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> = host::entry(py, AdapterHost::TableFreshnessRequest)?; @@ -462,7 +466,11 @@ fn observe_adapter_batch_inner<'py>( )?; } if requests.is_empty() { - return call_with(&batch_class, &[("unknown", unknown_sources.into_any())]); + return new_record_with( + py, + RecordId::AdapterSourceFreshnessBatch, + &[("unknown", unknown_sources.into_any())], + ); } let unique: Bound<'py, PyDict> = PyDict::new(py); for request in requests.values() { @@ -502,8 +510,9 @@ fn observe_adapter_batch_inner<'py>( metadata_observation(&name, &metadata, &observer.observed_at)?, )?; } - call_with( - &batch_class, + new_record_with( + py, + RecordId::AdapterSourceFreshnessBatch, &[ ("observations", observations.into_any()), ("unknown", unknown_sources.into_any()), @@ -529,8 +538,9 @@ pub(crate) fn observation_record<'py>( &value_kind.extract::()?, &data_version.extract::()?, )?; - call_with( - ®istry::class(py, RecordId::SourceFreshnessRecord)?, + new_record_with( + py, + RecordId::SourceFreshnessRecord, &[ ("source_name", source_name), ("target_database", native_field(source, "database")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/freshness/planning.rs b/crates/sqlbuild-python/src/bindings/_helpers/freshness/planning.rs index 7f3fb613a0..8eaaf668f6 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/freshness/planning.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/freshness/planning.rs @@ -7,7 +7,7 @@ use pyo3::types::{PyDict, PyDictMethods, PyFrozenSet, PySet, PySetMethods, PyTup use pyo3::{pyfunction, wrap_pyfunction}; use sqlbuild_state::freshness::main::configuration_error::configuration_error; -use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; use crate::bindings::_helpers::freshness::exception_types::SourceFreshnessObservationError; use crate::bindings::_helpers::freshness::observation::{ Observer, observation_record, observation_source, observe_adapter_batch, observe_configured, @@ -18,9 +18,9 @@ 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::construct::{enum_member, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::state_rows::freshness::{age_policy, equivalent}; const ADAPTER_STRATEGY: &str = "adapter"; @@ -125,8 +125,9 @@ fn adapter_batch<'py>( let name: Bound<'py, PyAny> = native_field(source, "name")?; unknown_sources.set_item(&name, unknown(&name, ("ERROR", message.clone()))?)?; } - call_with( - ®istry::class(py, RecordId::AdapterSourceFreshnessBatch)?, + new_record_with( + py, + RecordId::AdapterSourceFreshnessBatch, &[("unknown", unknown_sources.into_any())], ) } @@ -268,8 +269,9 @@ pub(crate) fn build_direct_source_freshness_planning_result<'py>( } } let unknown_names: Bound<'py, PyDict> = sorted_dict(py, &unknown_sources)?; - call_with( - ®istry::class(py, RecordId::DirectSourceFreshnessPlanningResult)?, + new_record_with( + py, + RecordId::DirectSourceFreshnessPlanningResult, &[ ( "observed_records", @@ -301,12 +303,13 @@ fn downstream_models<'py>( downstream_deps: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = source_name.py(); - let key: Bound<'py, PyAny> = call_with( - ®istry::class(py, RecordId::CompiledObjectKey)?, + let key: Bound<'py, PyAny> = new_record_with( + py, + RecordId::CompiledObjectKey, &[ ( "resource_type", - registry::class(py, RecordId::CompiledResourceType)?.getattr("SOURCE")?, + enum_member(py, RecordId::CompiledResourceType, "SOURCE")?, ), ("name", source_name.clone()), ], @@ -345,7 +348,7 @@ pub(crate) fn build_direct_source_freshness_propagation_result<'py>( unknown_sources.set_item(name, models)?; } let error_status: Bound<'py, PyAny> = - registry::class(py, RecordId::SourceFreshnessAgeStatus)?.getattr("ERROR")?; + enum_member(py, RecordId::SourceFreshnessAgeStatus, "ERROR")?; for (identity, status) in source_freshness .getattr("age_statuses")? .downcast::()? @@ -359,8 +362,9 @@ pub(crate) fn build_direct_source_freshness_propagation_result<'py>( blocked.call_method1("update", (&models,))?; errors.set_item(identity, models)?; } - call_with( - ®istry::class(py, RecordId::DirectSourceFreshnessPropagationResult)?, + new_record_with( + 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 5606688024..e96faf23ab 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/freshness/publication.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/freshness/publication.rs @@ -12,9 +12,9 @@ use crate::bindings::_helpers::cost_telemetry::context::CostContext; use crate::bindings::_helpers::freshness::state_io::write_source_freshness_records; use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; /// Observed records grouped by `(database, schema)` in first-seen order. type RecordsByLocation<'py> = Vec<( @@ -154,8 +154,9 @@ fn eligible_records<'py>( /// `SourceFreshnessRenderers` bound to `adapter`. fn renderers<'py>(adapter: &Bound<'py, PyAny>) -> PyResult> { - call_with( - ®istry::class(adapter.py(), RecordId::SourceFreshnessRenderers)?, + new_record_with( + adapter.py(), + RecordId::SourceFreshnessRenderers, &[ ( "render_qualified_name", 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 d01d1d9175..df9f1f03fd 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/freshness/state_io.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/freshness/state_io.rs @@ -15,9 +15,9 @@ use crate::bindings::_helpers::cursor::values::DATETIME_MODULE; use crate::bindings::_helpers::freshness::support::{ INPUT_ERROR, exception_text, freshness_error, optional_str, presumed_utc_datetime, text, }; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; 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; @@ -110,8 +110,9 @@ pub(crate) struct StateRead<'py> { fn empty_set<'py>(schema: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = schema.py(); - call_with( - ®istry::class(py, RecordId::SourceFreshnessSet)?, + new_record_with( + py, + RecordId::SourceFreshnessSet, &[ ("schema", schema.clone()), ("records", PyDict::new(py).into_any()), @@ -174,8 +175,9 @@ freshness table to regenerate source freshness state." let record: Bound<'py, PyAny> = row_record(&row?)?; records.set_item(native_field(&record, "identity")?, record)?; } - call_with( - ®istry::class(py, RecordId::SourceFreshnessSet)?, + new_record_with( + py, + RecordId::SourceFreshnessSet, &[("schema", schema.clone()), ("records", records.into_any())], ) } @@ -195,8 +197,9 @@ fn row_record<'py>(row: &Bound<'py, PyAny>) -> PyResult> { }; let optional = |index: usize| -> PyResult> { optional_str(&row.get_item(index)?) }; - call_with( - ®istry::class(py, RecordId::SourceFreshnessRecord)?, + new_record_with( + py, + RecordId::SourceFreshnessRecord, &[ ("source_name", field(0)?), ("target_database", optional(1)?), 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 9c05ed2d96..9be48b4ae4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/frontier/compile_frontier.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/frontier/compile_frontier.rs @@ -8,8 +8,8 @@ use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, use crate::bindings::_helpers::frontier::stage_capture::write_stage_capture; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::native_host::user_code::UserCodeHost; +use crate::bindings::_helpers::records::construct::enum_member; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; /// Produce the frontier object for `until`, capturing it on request. #[pyfunction] @@ -65,7 +65,7 @@ pub(crate) fn frontier_stage(py: Python<'_>, stage: FrontierStage) -> PyResult "COMPILE_PROJECT_INPUTS", FrontierStage::CompiledProject => "COMPILED_PROJECT", }; - registry::class(py, RecordId::CompilerStage)?.getattr(member) + enum_member(py, RecordId::CompilerStage, member) } /// `stage()` run inside the macro bridge user macros render through. 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 319aeb6f8a..2c1d0ec1e1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/graph/execution_gates.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/graph/execution_gates.rs @@ -8,9 +8,9 @@ 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::construct::{enum_from_value, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; const PATHLIB_MODULE: &str = "pathlib"; /// The model config keys whose entries are hooks, in lifecycle order. @@ -36,7 +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> = registry::class(py, RecordId::AttachedAuditGateEdge)?; + let edge_type: Bound<'py, PyAny> = record_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( @@ -61,8 +61,7 @@ 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> = registry::class(py, RecordId::HookReadEdge)?; - let hook_type: Bound<'py, PyAny> = registry::class(py, RecordId::HookReadType)?; + let edge_type: Bound<'py, PyAny> = record_class(py, RecordId::HookReadEdge)?; 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 { @@ -76,7 +75,10 @@ fn hook_read_edges<'py>( ("hook_name", PyString::new(py, &hook_name).into_any()), ("gated", cache.key(&gated)?), ("read", cache.key(&read)?), - ("hook_type", hook_type.call1((kind,))?), + ( + "hook_type", + enum_from_value(py, RecordId::HookReadType, kind)?, + ), ("hook_path", hook_path), ], )?); @@ -91,7 +93,7 @@ fn model_python_hook_names<'py>( py: Python<'py>, model: &Bound<'py, PyAny>, ) -> PyResult> { - let entry_type: Bound<'_, PyAny> = registry::class(py, RecordId::PythonHookEntry)?; + let entry_type: Bound<'_, PyAny> = record_class(py, RecordId::PythonHookEntry)?; let values: Bound<'_, PyAny> = native_field(model, "config")?.getattr("values")?; let mut names: Vec = Vec::new(); for hook_key in MODEL_HOOK_KEYS { @@ -122,10 +124,13 @@ pub(crate) fn sql_ref_key<'py>( py: Python<'py>, reference: &Bound<'py, PyAny>, ) -> PyResult> { - let resource_type: Bound<'py, PyAny> = registry::class(py, RecordId::CompiledResourceType)? - .call1((native_field(reference, "kind")?.getattr("value")?,))?; + let resource_type: Bound<'py, PyAny> = enum_from_value( + py, + RecordId::CompiledResourceType, + native_field(reference, "kind")?.getattr("value")?, + )?; keyword_call( - ®istry::class(py, RecordId::CompiledObjectKey)?, + &record_class(py, RecordId::CompiledObjectKey)?, &[ ("resource_type", resource_type), ("name", native_field(reference, "name")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/graph/keys.rs b/crates/sqlbuild-python/src/bindings/_helpers/graph/keys.rs index a5d71ca98f..b2e2363db6 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/graph/keys.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/graph/keys.rs @@ -1,8 +1,8 @@ //! `CompiledObjectKey`s for native `(resource type, name)` pairs, converted once per pair. +use crate::bindings::_helpers::records::construct::record_class; use crate::bindings::_helpers::records::fields::field as native_field; 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}; @@ -16,8 +16,8 @@ pub(crate) struct KeyCache<'py> { impl<'py> KeyCache<'py> { pub(crate) fn new(py: Python<'py>) -> PyResult { Ok(Self { - key_type: registry::class(py, RecordId::CompiledObjectKey)?, - resource_type: registry::class(py, RecordId::CompiledResourceType)?, + key_type: record_class(py, RecordId::CompiledObjectKey)?, + resource_type: record_class(py, RecordId::CompiledResourceType)?, keys: PyDict::new(py), }) } 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 3127c88639..5cc6ca2556 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/graph/python_lifecycle.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/graph/python_lifecycle.rs @@ -2,9 +2,9 @@ use std::collections::{BTreeSet, HashMap}; +use crate::bindings::_helpers::records::construct::new_record; use crate::bindings::_helpers::records::fields::field as native_field; 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}; @@ -62,7 +62,12 @@ pub(crate) fn build_python_sql_run_lifecycle<'py>( "read_side_python_node_names", PyFrozenSet::new(py, lifecycle.read_side_names)?, )?; - registry::class(py, RecordId::PythonSqlRunLifecyclePlan)?.call((), Some(&arguments)) + new_record( + py, + RecordId::PythonSqlRunLifecyclePlan, + (), + 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 0eb247bf0d..8566be13dd 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 @@ -13,9 +13,9 @@ use crate::bindings::_helpers::graph::python_selection::resolve_python_sql_run_s 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::construct::{new_record_with, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; const NAME_KEY: &str = "name"; @@ -115,7 +115,7 @@ fn kind_metadata<'py>( .iter() .map(|field| Ok((*field, get(field)?))) .collect::>()?; - call_with(®istry::class(py, class)?, &pairs) + new_record_with(py, class, &pairs) }; Ok(match kind { "LOADER" => ( @@ -255,7 +255,7 @@ pub(crate) fn python_node_graph<'py>( providers.set_item(native_field(&provider, "name")?, provider)?; } let vocabulary: Vocabulary<'py> = Vocabulary { - node: registry::class(py, RecordId::DiscoveredPythonNode)?, + node: record_class(py, RecordId::DiscoveredPythonNode)?, kinds: host::entry(py, UserCodeHost::PythonNodeKind)?, sql_ref: host::entry(py, UserCodeHost::SqlResourceRef)?, providers, @@ -288,12 +288,12 @@ fn graph_record<'py>( tags, paths, } = indexes; - let edge_class: Bound<'py, PyAny> = registry::class(py, RecordId::PythonNodeDependencyEdge)?; let dependency_edges: Vec> = edges .into_iter() .map(|(upstream_name, downstream_name)| { - call_with( - &edge_class, + new_record_with( + py, + RecordId::PythonNodeDependencyEdge, &[ ( "upstream_name", @@ -330,8 +330,9 @@ fn graph_record<'py>( by_name.set_item(&name, node)?; by_selector.set_item(format!("{kind}:{}", name.str()?), node)?; } - call_with( - ®istry::class(py, RecordId::PythonNodeGraph)?, + new_record_with( + py, + RecordId::PythonNodeGraph, &[ ("nodes", PyTuple::new(py, nodes)?.into_any()), ( @@ -358,8 +359,9 @@ pub(crate) fn run_selection_from_inputs<'py>( let python_graph: Bound<'py, PyAny> = python_node_graph(discovered_inputs)?; let (sql_keys, python_node_names) = resolve_python_sql_run_selection(project_graph, &python_graph, select, exclude)?; - call_with( - ®istry::class(py, RecordId::PythonSqlRunSelection)?, + new_record_with( + py, + RecordId::PythonSqlRunSelection, &[ ("sql_keys", compiled_graph_keys(py, sql_keys)?.into_any()), ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/graph/views.rs b/crates/sqlbuild-python/src/bindings/_helpers/graph/views.rs index 74cbc632aa..9567961880 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/graph/views.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/graph/views.rs @@ -12,9 +12,9 @@ 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; +use crate::bindings::_helpers::records::construct::new_record; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; -use crate::bindings::_helpers::records::registry; /// The native graph's `(resource type, name)` pair for `key`. #[pyfunction] @@ -85,7 +85,7 @@ fn graph_views<'py>(py: Python<'py>, graph: &ProjectGraph) -> PyResult( inferred = native_field(&proof, "fixed_columns")?; has_star = true_object(py); } - let resource_type: Object<'py> = registry::class(py, RecordId::CompiledResourceType)?; - let key_class: Object<'py> = registry::class(py, RecordId::CompiledObjectKey)?; + let resource_type: Object<'py> = record_class(py, RecordId::CompiledResourceType)?; + let key_class: Object<'py> = record_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()?; dependency_keys.push(call( &key_class, &[ - ("resource_type", resource_type.call1((kind,))?), + ( + "resource_type", + enum_from_value(py, RecordId::CompiledResourceType, kind)?, + ), ("name", name), ], )?); @@ -88,7 +92,7 @@ pub(crate) fn compiled_model<'py>( ], )?; call( - ®istry::class(py, RecordId::CompiledModel)?, + &record_class(py, RecordId::CompiledModel)?, &[ ("key", key), ("deps", PyTuple::new(py, dependency_keys)?.into_any()), @@ -155,7 +159,7 @@ fn destination<'py>(config: &Object<'py>, model_name: &Object<'py>) -> PyResult< }; let none: Object<'py> = py.None().into_bound(py); call( - ®istry::class(py, RecordId::CompiledRelationLocation)?, + &record_class(py, RecordId::CompiledRelationLocation)?, &[ ( "database", @@ -179,8 +183,7 @@ fn fallback_columns<'py>(rows: &Object<'py>) -> PyResult> { if rows.is_none() { return Ok(rows.clone()); } - let column_class: Object<'py> = registry::class(py, RecordId::InferredColumn)?; - let nullability: Object<'py> = registry::class(py, RecordId::InferredNullability)?; + let column_class: Object<'py> = record_class(py, RecordId::InferredColumn)?; let mut columns: Vec> = Vec::new(); for row in rows.try_iter()? { let (name, data_type, value): (Object<'py>, Object<'py>, Object<'py>) = row?.extract()?; @@ -189,7 +192,10 @@ fn fallback_columns<'py>(rows: &Object<'py>) -> PyResult> { &[ ("name", name), ("type", data_type), - ("nullability", nullability.call1((value,))?), + ( + "nullability", + enum_from_value(py, RecordId::InferredNullability, value)?, + ), ], )?); } @@ -257,11 +263,10 @@ fn located<'py>( }; let located: Object<'py> = registry::native(py, NativeAttr::LocateModelBindingDiagnostics)? .call1((PyList::new(py, rows)?, texts, expansion_row))?; - 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(py, RecordId::DiagnosticPhase)?.getattr("COMPILE")?; - let model: Object<'py> = - registry::class(py, RecordId::CompiledResourceType)?.getattr("MODEL")?; + let diagnostic_class: Object<'py> = record_class(py, RecordId::CompilerDiagnostic)?; + let location_class: Object<'py> = record_class(py, RecordId::SourceLocation)?; + let phase: Object<'py> = enum_member(py, RecordId::DiagnosticPhase, "COMPILE")?; + let model: Object<'py> = enum_member(py, RecordId::CompiledResourceType, "MODEL")?; let name: Object<'py> = native_field(&model_file, "file_path")?.getattr("stem")?; let mut built: Vec> = Vec::new(); for row in located.try_iter()? { 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 35c5a7f9f0..afe4362701 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_command.rs @@ -31,6 +31,7 @@ use crate::bindings::_helpers::janitor::janitor_phases::{ use crate::bindings::_helpers::janitor::janitor_plan::{janitor_plan_text, janitor_result_counts}; use crate::bindings::_helpers::planner_phases::objects::object; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; +use crate::bindings::_helpers::records::construct::new_record; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -114,7 +115,7 @@ fn invocation<'py>(py: Python<'py>, request: &Bound<'py, PyAny>) -> PyResult( ), ], )?; - registry::class(py, RecordId::JanitorSettings)?.call((), Some(&options)) + new_record(py, RecordId::JanitorSettings, (), Some(&options)) } /// Ask for the exact confirmation phrase; an interrupt cancels. 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 d2110ed1d0..77ae7ba7d2 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 @@ -16,6 +16,7 @@ use crate::bindings::_helpers::janitor::janitor_facts::{ use crate::bindings::_helpers::janitor::janitor_keys::{ Folded, Key, SchemaKey, optional, record, sorted_schemas, text, tuple, }; +use crate::bindings::_helpers::records::construct::enum_member; use crate::bindings::_helpers::state_rows::migration_io::{ read_histories as read_view_histories, stored_columns_binding, }; @@ -25,7 +26,6 @@ use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::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"; @@ -37,7 +37,7 @@ pub(crate) struct OldNamePlanning<'py> { } fn drop_reason<'py>(py: Python<'py>, member: &str) -> PyResult> { - registry::class(py, RecordId::OldNameViewDropReason)?.getattr(member) + enum_member(py, RecordId::OldNameViewDropReason, member) } fn status<'py>( @@ -48,7 +48,7 @@ fn status<'py>( } fn status_is(py: Python<'_>, value: &Bound<'_, PyAny>, member: &str) -> PyResult { - value.eq(registry::class(py, RecordId::OldNameViewStatus)?.getattr(member)?) + value.eq(enum_member(py, RecordId::OldNameViewStatus, member)?) } fn read_histories<'py>( 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 463de3a49d..2d18f3337b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_command.rs @@ -39,6 +39,7 @@ 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::construct::record_class; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -478,7 +479,7 @@ fn selector_anchors( ) -> PyResult { 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 path_class: Bound<'_, PyAny> = record_class(py, RecordId::PathSelector)?; let names: HashMap<&str, &LineageKey> = name_lookup(index); let named = |name: String| -> PyResult { names 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 f6c960c321..c427f67f25 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_index.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_index.rs @@ -2,9 +2,9 @@ use std::collections::HashMap; +use crate::bindings::_helpers::records::construct::{new_record, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; 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}; @@ -31,7 +31,7 @@ pub(crate) struct PythonKeys<'py> { impl<'py> PythonKeys<'py> { pub(crate) fn new(py: Python<'py>) -> PyResult { Ok(Self { - class: registry::class(py, RecordId::CompiledObjectKey)?, + class: record_class(py, RecordId::CompiledObjectKey)?, made: HashMap::new(), }) } @@ -219,5 +219,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)?; - registry::class(py, RecordId::LineageGraphViews)?.call((), Some(&options)) + new_record(py, RecordId::LineageGraphViews, (), 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 e8bee6abb6..bb45d5359d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_objects.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_objects.rs @@ -2,9 +2,9 @@ use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::construct::record_class; use crate::bindings::_helpers::records::fields::field as native_field; 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, PyTuple}; use sqlbuild_analysis::lineage::models::{LineageColumn, LineageSource}; @@ -34,21 +34,21 @@ pub(crate) struct LineageClasses<'py> { impl<'py> LineageClasses<'py> { pub(crate) fn new(py: Python<'py>) -> PyResult { - let resource_type: Bound<'py, PyAny> = registry::class(py, RecordId::CompiledResourceType)?; - let nullability: Bound<'py, PyAny> = registry::class(py, RecordId::InferredNullability)?; + let resource_type: Bound<'py, PyAny> = record_class(py, RecordId::CompiledResourceType)?; + let nullability: Bound<'py, PyAny> = record_class(py, RecordId::InferredNullability)?; Ok(Self { - 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(py, RecordId::ColumnTransformKind)?, - confidence: registry::class(py, RecordId::ColumnLineageConfidence)?, - analysis_dialect: registry::class(py, RecordId::PolyglotAnalysisDialect)?, + qualified_column: record_class(py, RecordId::QualifiedLineageColumn)?, + lineage_source: record_class(py, RecordId::ColumnLineageSource)?, + lineage_edge: record_class(py, RecordId::ColumnLineageEdge)?, + column_lineage: record_class(py, RecordId::ColumnLineage)?, + model_lineage: record_class(py, RecordId::ModelColumnLineage)?, + semantic_use: record_class(py, RecordId::DirectSemanticColumnUse)?, + column_fact: record_class(py, RecordId::CompiledLineageColumnFact)?, + source_fact: record_class(py, RecordId::CompiledLineageSourceFact)?, + compact_facts: record_class(py, RecordId::CompactLineageFacts)?, + transform_kind: record_class(py, RecordId::ColumnTransformKind)?, + confidence: record_class(py, RecordId::ColumnLineageConfidence)?, + analysis_dialect: record_class(py, RecordId::PolyglotAnalysisDialect)?, model_resource: native_field(&resource_type, "MODEL")?, unknown_nullability: native_field(&nullability, "UNKNOWN")?, nullability, 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 5ef2ef5906..b5ac4dab09 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/load/load_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/load/load_command.rs @@ -32,6 +32,7 @@ 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::construct::new_record; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -321,7 +322,7 @@ fn prepare<'py>( let cursor_overrides: Bound<'py, PyAny> = if typed.is_truthy()? { typed } else { - registry::class(py, RecordId::CursorOverrides)?.call0()? + new_record(py, RecordId::CursorOverrides, (), None)? }; let provider_session: Bound<'py, PyAny> = call_with( &host::entry(py, UserCodeHost::BuildProviderSession)?, 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 26feb7a965..16d497cd60 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/load/load_selection.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/load/load_selection.rs @@ -17,6 +17,7 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ }; use crate::bindings::_helpers::native_host::adapter::AdapterHost; use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -137,8 +138,9 @@ impl<'py> LoadProject<'py> { let meta: Bound<'py, PyDict> = PyDict::new(py); meta.set_item(LOADER_NODE_KEY, true)?; let name_value: Bound<'py, PyAny> = PyString::new(py, name).into_any(); - call_with( - ®istry::class(py, RecordId::SourceEntry)?, + new_record_with( + py, + RecordId::SourceEntry, &[ ("name", name_value.clone()), ("database", native_field(&parts, "database")?), From 7d97f99ef897e9fb31d1d2a19e67b15950d3609e Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Sat, 17 Oct 2026 01:57:45 +0100 Subject: [PATCH 18/28] refactor: expose native entry points to direct Rust callers Raise visibility of pyfunctions in plan, refactor, result, rules and workspace bindings that command bindings now call directly. --- .../bindings/_helpers/planner_entries/clone_plans.rs | 2 +- .../bindings/_helpers/planner_entries/command_plans.rs | 2 +- .../bindings/_helpers/planner_entries/display_plan.rs | 2 +- .../src/bindings/_helpers/planner_entries/mod.rs | 4 ++-- .../src/bindings/_helpers/refactor/contract_compare.rs | 4 +++- .../src/bindings/_helpers/refactor/contract_edits.rs | 6 +++--- .../src/bindings/_helpers/result_output/documents.rs | 10 +++++----- .../src/bindings/_helpers/rules/fixture_formatting.rs | 2 +- .../bindings/_helpers/workspace_commands/commands.rs | 5 ++++- 9 files changed, 21 insertions(+), 16 deletions(-) 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 933511f7f0..39491c9580 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 @@ -454,7 +454,7 @@ fn origin_source_entries<'py>( /// Prepare planner outputs needed for clone execution. #[pyfunction] #[pyo3(signature = (**kwargs))] -fn run_clone_planning<'py>( +pub(crate) fn run_clone_planning<'py>( py: Python<'py>, kwargs: Option>, ) -> PyResult> { 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 be2b1736f1..daf5f0b783 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 @@ -147,7 +147,7 @@ pub(crate) fn resolve_static_relation_context<'py>( /// Project selected audits from canonical static state. #[pyfunction] #[pyo3(signature = (*, project, adapter, scope, relations))] -fn build_audit_command_plan<'py>( +pub(crate) fn build_audit_command_plan<'py>( project: &Bound<'py, PyAny>, adapter: &Bound<'py, PyAny>, scope: &Bound<'py, PyAny>, 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 c29a356f48..2caf473c20 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 @@ -127,7 +127,7 @@ fn display_entry<'py>( /// Build a display-only plan when warehouse planning cannot fully resolve. #[pyfunction] #[pyo3(signature = (*, project, selected_model_names, full_refresh))] -fn build_display_only_sqlbuild_plan<'py>( +pub(crate) fn build_display_only_sqlbuild_plan<'py>( project: &Bound<'py, PyAny>, selected_model_names: &Bound<'py, PyAny>, full_refresh: bool, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/mod.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/mod.rs index a8dddfd37f..145ad2dc23 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/mod.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_entries/mod.rs @@ -1,9 +1,9 @@ //! Plan entry and plan output assembly over native planner resolution. -mod clone_plans; +pub(crate) mod clone_plans; pub(crate) mod command_plans; pub(crate) mod contexts; -mod display_plan; +pub(crate) mod display_plan; pub(crate) mod execution_plan; pub(crate) mod model_entries; pub(crate) mod models; 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 4d7ebe0aaf..cb899e603c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/refactor/contract_compare.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/refactor/contract_compare.rs @@ -458,7 +458,9 @@ fn relation_info<'py>(item: &Bound<'py, PyAny>) -> PyResult> { /// Selected declarations compared with one bulk read of physical columns. #[pyfunction] #[pyo3(signature = (**request))] -fn compare_contracts<'py>(request: Option<&Bound<'py, PyDict>>) -> PyResult> { +pub(crate) fn compare_contracts<'py>( + request: Option<&Bound<'py, PyDict>>, +) -> PyResult> { let request = Request::new("compare_contracts", request)?; let py: Python<'py> = request.py(); let adapter: &Bound<'py, PyAny> = &request.get("adapter")?; 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 fc57be5300..b91f063a45 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/refactor/contract_edits.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/refactor/contract_edits.rs @@ -71,7 +71,7 @@ fn physical_rows(columns: &Bound<'_, PyAny>) -> PyResult> { /// The model's MODEL header with physical columns adopted, or the ownership conflict. #[pyfunction] #[pyo3(signature = (*, model, physical_columns, overwrite))] -fn edit_contract_model( +pub(crate) fn edit_contract_model( py: Python<'_>, model: &Bound<'_, PyAny>, physical_columns: &Bound<'_, PyAny>, @@ -117,7 +117,7 @@ fn edit_contract_model( /// The source's YAML declaration with physical columns adopted, or the ownership conflict. #[pyfunction] #[pyo3(signature = (*, source, physical_columns, overwrite, contents = None))] -fn edit_contract_source( +pub(crate) fn edit_contract_source( source: &Bound<'_, PyAny>, physical_columns: &Bound<'_, PyAny>, overwrite: bool, @@ -148,7 +148,7 @@ fn edit_contract_source( /// The findings an edit leaves, plus an ownership-conflict finding when the edit was refused. #[pyfunction] #[pyo3(signature = (*, findings, overwrite, conflict))] -fn remaining_contract_findings<'py>( +pub(crate) fn remaining_contract_findings<'py>( py: Python<'py>, findings: &Bound<'py, PyAny>, overwrite: bool, 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 420aa0355f..29dacfc6df 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/result_output/documents.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/result_output/documents.rs @@ -182,7 +182,7 @@ fn count(result: &Bound<'_, PyAny>, field: &str) -> PyResult { /// Format build command execution results as JSON. #[pyfunction] #[pyo3(signature = (**options))] -fn format_build_execution_json<'py>( +pub(crate) fn format_build_execution_json<'py>( py: Python<'py>, options: Option>, ) -> PyResult { @@ -225,7 +225,7 @@ fn audit_projection<'py>( /// The build document with its audit-history projection, or a minimal degraded document. #[pyfunction] #[pyo3(signature = (**options))] -fn format_degradable_build_execution_json<'py>( +pub(crate) fn format_degradable_build_execution_json<'py>( py: Python<'py>, options: Option>, ) -> PyResult { @@ -412,7 +412,7 @@ pub(crate) fn format_seed_execution_json<'py>( /// Format load command execution results as JSON. #[pyfunction] #[pyo3(signature = (*, results))] -fn format_load_execution_json<'py>( +pub(crate) fn format_load_execution_json<'py>( py: Python<'py>, results: Bound<'py, PyAny>, ) -> PyResult { @@ -443,7 +443,7 @@ fn format_load_execution_json<'py>( /// Format direct clone command execution results as JSON. #[pyfunction] #[pyo3(signature = (*, result, resource_types_by_name))] -fn format_clone_execution_json<'py>( +pub(crate) fn format_clone_execution_json<'py>( py: Python<'py>, result: Bound<'py, PyAny>, resource_types_by_name: Bound<'py, PyAny>, @@ -518,7 +518,7 @@ pub(crate) fn format_test_execution_json<'py>( /// Format audit command execution results as JSON. #[pyfunction] #[pyo3(signature = (*, results, configured_concurrency = None, worker_count = None))] -fn format_audit_execution_json<'py>( +pub(crate) fn format_audit_execution_json<'py>( py: Python<'py>, results: Bound<'py, PyAny>, configured_concurrency: Option>, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/fixture_formatting.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/fixture_formatting.rs index 154e27d114..0e03ecccca 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/fixture_formatting.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/fixture_formatting.rs @@ -25,7 +25,7 @@ fn simplify_fixture_null_projections(sql: &str, relation: &Bound<'_, PyAny>) -> #[pyfunction] #[pyo3(signature = (*, files, project_dir, discovered_inputs, sql_lexical_syntax))] -fn format_redundant_fixture_nulls<'py>( +pub(crate) fn format_redundant_fixture_nulls<'py>( files: &Bound<'py, PyDict>, project_dir: &Bound<'py, PyAny>, discovered_inputs: &Bound<'py, PyAny>, 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 88558274b3..267ef7aa5c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/workspace_commands/commands.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/workspace_commands/commands.rs @@ -127,7 +127,10 @@ fn update_sqlbuild_skills<'py>( /// Check or refresh generated project skills without changing command success. #[pyfunction] #[pyo3(signature = (*, project_dir))] -fn maintain_sqlbuild_skills(py: Python<'_>, project_dir: PathBuf) -> PyResult> { +pub(crate) fn maintain_sqlbuild_skills( + py: Python<'_>, + project_dir: PathBuf, +) -> PyResult> { let message: String = maintain_skills(&project_dir, &package_dir(py)?.join(SKILLS_DIRECTORY)) .map_err(|error| raised(py, error))?; let keywords: Bound<'_, PyDict> = PyDict::new(py); From ecc0ad8d5ede6f5aa504a35c41382c7190335e1b Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Sat, 17 Oct 2026 01:57:45 +0100 Subject: [PATCH 19/28] refactor: call native entry points directly in audit_command to command_runtime bindings Replace keyword calls through sqlbuild._native attributes with direct Rust calls to the pyfunctions. Entry points that tests replace on the module are left for a follow-up test rewrite. --- .../_helpers/audit_command/command.rs | 71 ++++++------ .../_helpers/build_command/completion.rs | 105 ++++++++++-------- .../_helpers/build_command/execution.rs | 99 ++++++++++------- .../_helpers/build_command/invocation.rs | 45 +++----- .../_helpers/build_command/lifecycle.rs | 66 ++++++----- .../_helpers/build_command/planning.rs | 92 ++++++++------- .../_helpers/build_command/policies.rs | 2 +- .../_helpers/build_progress/footer.rs | 2 +- .../bindings/_helpers/check/check_results.rs | 5 +- .../src/bindings/_helpers/cli/dispatch.rs | 16 +-- .../src/bindings/_helpers/cli/entry.rs | 46 ++++---- .../bindings/_helpers/cli/progress_text.rs | 6 +- .../bindings/_helpers/clone/clone_command.rs | 100 ++++++++--------- .../_helpers/clone/clone_execution.rs | 22 +--- .../bindings/_helpers/clone/clone_pipeline.rs | 35 +++--- .../_helpers/command_runtime/connection.rs | 41 +++---- .../_helpers/command_runtime/warehouses.rs | 37 +++--- 17 files changed, 404 insertions(+), 386 deletions(-) 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 3d62d9276b..005a11b0f9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/audit_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/audit_command/command.rs @@ -12,13 +12,18 @@ 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, within}; +use crate::bindings::_helpers::compile_driver::pipeline_entries::build_static_pipeline_result; +use crate::bindings::_helpers::compile_driver::python_calls::{call_with, keywords, within}; +use crate::bindings::_helpers::compile_driver::selection_phases::compile_static_command_context; use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::planner_entries::command_plans::build_audit_command_plan; use crate::bindings::_helpers::records::construct::{new_record_with, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::result_output::documents::format_audit_execution_json; +use crate::bindings::_helpers::result_output::documents::write_execution_json_output; const EXECUTION_COMMAND: &str = "sqb audit"; const PASS_OUTCOME: &str = "pass"; @@ -135,8 +140,8 @@ fn compile_audits<'py>( ("external_sql_reference_resolver", resolver), ], )?; - let context: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::CompileStaticCommandContext)?, + let context: Bound<'py, PyAny> = compile_static_command_context(Some(&keywords( + py, &[ ("discovered_inputs", discovered), ("adapter", adapter.clone()), @@ -148,19 +153,19 @@ fn compile_audits<'py>( .getattr("on_progress")?, ), ], + )?))?; + let plan_output: Bound<'py, PyAny> = build_audit_command_plan( + &native_field(&context, "project")?, + &adapter, + &native_field(&context, "scope")?, + &native_field(&context, "relations")?, )?; - let plan_output: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::BuildAuditCommandPlan)?, - &[ - ("project", native_field(&context, "project")?), - ("adapter", adapter), - ("scope", native_field(&context, "scope")?), - ("relations", native_field(&context, "relations")?), - ], - )?; - call_with( - ®istry::native(py, NativeAttr::BuildStaticPipelineResult)?, - &[("context", context), ("plan_output", plan_output)], + build_static_pipeline_result( + py, + Some(keywords( + py, + &[("context", context), ("plan_output", plan_output)], + )?), ) } @@ -400,17 +405,13 @@ fn audit_payload<'py>( (concurrency, worker_count): (i64, i64), ) -> PyResult> { let py: Python<'py> = results.py(); - let payload: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::FormatAuditExecutionJson)?, - &[ - ("results", results.clone()), - ( - "configured_concurrency", - PyInt::new(py, concurrency).into_any(), - ), - ("worker_count", PyInt::new(py, worker_count).into_any()), - ], - )?; + let payload: Bound<'py, PyAny> = format_audit_execution_json( + py, + results.clone(), + Some(PyInt::new(py, concurrency).into_any()).filter(|value| !value.is_none()), + Some(PyInt::new(py, worker_count).into_any()).filter(|value| !value.is_none()), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; let projection: Bound<'py, PyAny> = host::entry(py, ExecutorHost::CurrentAuditResultProjection)?.call0()?; if projection.is_none() { @@ -478,17 +479,13 @@ fn complete( &PyTuple::new(py, results)?.into_any(), (run.concurrency, run.worker_count), )?; - call_with( - ®istry::native(py, NativeAttr::WriteExecutionJsonOutput)?, - &[ - ("payload", payload), - ("json_output", native_field(request, "json_output")?), - ( - "json_output_path", - native_field(request, "json_output_path")?, - ), - ], - )?; + write_execution_json_output( + py, + payload, + native_field(request, "json_output")?.extract::()?, + native_field(request, "json_output_path")?, + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; Ok(audit_exit_code(counts.failed)) } 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 494e2b82cd..1d9fb5d2da 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/completion.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/completion.rs @@ -17,19 +17,24 @@ 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; +use crate::bindings::_helpers::build_progress::footer::format_build_footer; +use crate::bindings::_helpers::compile_driver::pipeline_entries::plan_has_executable_work; +use crate::bindings::_helpers::compile_driver::python_calls::{call_with, keywords}; 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::cost_report::functions::cost_output_payload; use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::result_output::documents::format_build_execution_json; +use crate::bindings::_helpers::result_output::documents::format_degradable_build_execution_json; const SUCCESS_STATUS: &str = "success"; const PENDING_MESSAGE: &str = "Build cost collection has not completed."; @@ -156,10 +161,8 @@ fn cost_payload<'py>(py: Python<'py>, record: &Bound<'py, PyAny>) -> PyResult( @@ -194,16 +197,20 @@ fn write_json(context: &BuildContext<'_>, payload: Bound<'_, PyAny>) -> PyResult fn finalize_no_work(context: &BuildContext<'_>, pipeline: &Bound<'_, PyAny>) -> PyResult { let py: Python<'_> = context.py(); let plan: Bound<'_, PyAny> = native_field(pipeline, "plan_output")?; - if call_with( - ®istry::native(py, NativeAttr::PlanHasExecutableWork)?, - &[ - ("plan", plan.clone()), - ( - "python_plan_entries", - native_field(pipeline, "python_plan_entries")?, - ), - ], - )? + if plan_has_executable_work( + py, + Some(keywords( + py, + &[ + ("plan", plan.clone()), + ( + "python_plan_entries", + native_field(pipeline, "python_plan_entries")?, + ), + ], + )?), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))? .is_truthy()? { return Ok(false); @@ -232,15 +239,19 @@ fn finalize_no_work(context: &BuildContext<'_>, pipeline: &Bound<'_, PyAny>) -> host::entry(py, ExecutorHost::BuildStatus)?.getattr("SUCCESS")?, )], )?; - let payload: Bound<'_, PyAny> = call_with( - ®istry::native(py, NativeAttr::FormatBuildExecutionJson)?, - &[ - ("result", result), - ("plan", plan), - ("run_id", run_id_for(pipeline, &record)?), - ("cost", cost_payload(py, &record)?), - ], - )?; + let payload: Bound<'_, PyAny> = format_build_execution_json( + py, + Some(keywords( + py, + &[ + ("result", result), + ("plan", plan), + ("run_id", run_id_for(pipeline, &record)?), + ("cost", cost_payload(py, &record)?), + ], + )?), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; write_json(context, payload)?; Ok(true) } @@ -353,30 +364,32 @@ 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( - ®istry::native(py, NativeAttr::FormatBuildFooter)?, - &[ - ("result", result.clone()), - ("elapsed", native_field(&preparation.callbacks, "elapsed")?), - ("use_color", boolean(py, context.use_color)), - ("python_node_results", outcome.python_results.clone()), - ], - )? + let footer: String = format_build_footer( + &result.clone(), + native_field(&preparation.callbacks, "elapsed")?.extract::()?, + boolean(py, context.use_color).extract::()?, + Some(&outcome.python_results.clone()).filter(|value| !value.is_none()), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))? .extract()?; emit(&context.stream, &format!("\n{footer}\n"))?; - let payload: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::FormatDegradableBuildExecutionJson)?, - &[ - ("result", result.clone()), - ("plan", plan), - ("python_node_results", outcome.python_results.clone()), - ("python_check_results", check_results.clone()), - ("command", text(py, "build")), - ("run_id", run_id_for(pipeline, &record)?), - ("cost", cost_payload(py, &record)?), - ("execution_succeeded", boolean(py, exit_code == 0)), - ], - )?; + let payload: Bound<'py, PyAny> = format_degradable_build_execution_json( + py, + Some(keywords( + py, + &[ + ("result", result.clone()), + ("plan", plan), + ("python_node_results", outcome.python_results.clone()), + ("python_check_results", check_results.clone()), + ("command", text(py, "build")), + ("run_id", run_id_for(pipeline, &record)?), + ("cost", cost_payload(py, &record)?), + ("execution_succeeded", boolean(py, exit_code == 0)), + ], + )?), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; write_json(context, payload)?; render_build_cost(&record, &context.stream, context.use_color)?; if context.verbose { 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 992cb1edd1..27bad89003 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/execution.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/execution.rs @@ -18,7 +18,11 @@ 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, within}; +use crate::bindings::_helpers::check::check_results::write_check_results; +use crate::bindings::_helpers::cli::progress_text::write_execution_header; +use crate::bindings::_helpers::command_runtime::warehouses::describe_connection_warehouse; +use crate::bindings::_helpers::compile_driver::python_calls::{call_with, keywords, within}; +use crate::bindings::_helpers::compile_driver::relation_targets::build_project_relation_targets; 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; @@ -101,16 +105,11 @@ fn run_context_facts( + loaded_sources + count(&discovered, "task_functions")? + count(&discovered, "asset_functions")?; - let resolved: Bound<'_, PyAny> = call_with( - ®istry::native(py, NativeAttr::DescribeConnectionWarehouse)?, - &[ - ("discovered_inputs", discovered), - ( - "selected_target", - native_field(&project, "effective_target_name")?, - ), - ("connection_config", context.get("connection_config")?), - ], + let resolved: Bound<'_, PyAny> = describe_connection_warehouse( + py, + discovered, + native_field(&project, "effective_target_name")?, + context.get("connection_config")?, )?; let warehouse: Bound<'_, PyAny> = native_field(&resolved, "warehouse")?; let warehouse: Option<(String, String)> = if warehouse.is_none() { @@ -224,16 +223,20 @@ pub(crate) fn prepare_build_execution<'py>( if context.verbose { write_run_context(context, pipeline)?; } else { - call_with( - ®istry::native(py, NativeAttr::WriteExecutionHeader)?, - &[ - ("stream", context.stream.clone()), - ("command", text(py, EXECUTION_COMMAND)), - ("target", none(py)), - ("concurrency", concurrency.clone()), - ("use_color", boolean(py, context.use_color)), - ], - )?; + write_execution_header( + py, + Some(keywords( + py, + &[ + ("stream", context.stream.clone()), + ("command", text(py, EXECUTION_COMMAND)), + ("target", none(py)), + ("concurrency", concurrency.clone()), + ("use_color", boolean(py, context.use_color)), + ], + )?), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; } let connection_progress: Bound<'py, PyAny> = call_with( ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, @@ -325,10 +328,14 @@ pub(crate) fn execute_build_plan<'py>( ("providers", providers.clone()), ( "project_relations", - call_with( - ®istry::native(py, NativeAttr::BuildProjectRelationTargets)?, - &[("adapter", adapter.clone()), ("plan_output", plan.clone())], - )?, + build_project_relation_targets( + py, + Some(keywords( + py, + &[("adapter", adapter.clone()), ("plan_output", plan.clone())], + )?), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?, ), ], )?; @@ -458,13 +465,17 @@ fn check_runtime<'py>( ), ( "project_relations", - call_with( - ®istry::native(py, NativeAttr::BuildProjectRelationTargets)?, - &[ - ("adapter", adapter), - ("plan_output", native_field(pipeline, "plan_output")?), - ], - )?, + build_project_relation_targets( + py, + Some(keywords( + py, + &[ + ("adapter", adapter), + ("plan_output", native_field(pipeline, "plan_output")?), + ], + )?), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?, ), ("providers", providers.clone()), ], @@ -559,16 +570,20 @@ pub(crate) fn run_post_build_python_checks<'py>( let closed: PyResult> = adapter.call_method1("close", (connection,)); let check_results: Bound<'py, PyAny> = executed?; closed?; - call_with( - ®istry::native(py, NativeAttr::WriteCheckResults)?, - &[ - ("stream", context.stream.clone()), - ("results", check_results.clone()), - ("use_color", boolean(py, context.use_color)), - ("check_functions", tuple(py, &checks)?), - ("python_graph", graph), - ], - )?; + write_check_results( + py, + Some(keywords( + py, + &[ + ("stream", context.stream.clone()), + ("results", check_results.clone()), + ("use_color", boolean(py, context.use_color)), + ("check_functions", tuple(py, &checks)?), + ("python_graph", graph), + ], + )?), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; Ok(check_results) } 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 49e84709ec..f8ef98ee8d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/invocation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/invocation.rs @@ -10,9 +10,12 @@ use sqlbuild_core::build_command::models::DurationLimit; use crate::bindings::_helpers::build_command::policies::build_command_error; 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_attachment::effective_config::build_effective_settings_config; use crate::bindings::_helpers::compile_driver::discovery::discover_project_inputs; use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::contracts::spec_resolution::resolve_target_config; +use crate::bindings::_helpers::contracts::spec_resolution::resolve_target_name; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; use crate::bindings::_helpers::records::construct::{new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; @@ -58,16 +61,11 @@ fn target_config<'py>( target_name: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = discovered.py(); - call_with( - ®istry::native(py, NativeAttr::ResolveTargetConfig)?, - &[ - ( - "project_config", - native_field(discovered, "project_config")?, - ), - ("local_config", native_field(discovered, "local_config")?), - ("target_name", target_name.clone()), - ], + resolve_target_config( + py, + &native_field(discovered, "project_config")?, + &native_field(discovered, "local_config")?, + &target_name.clone(), ) } @@ -119,17 +117,13 @@ pub(crate) fn resolve_build_context<'py>( )?), )?; let selected_target: Bound<'py, PyAny> = native_field(request, "selected_target")?; - let target_name: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::ResolveTargetName)?, - &[ - ( - "project_config", - native_field(&discovered, "project_config")?, - ), - ("local_config", native_field(&discovered, "local_config")?), - ("selected_target", selected_target.clone()), - ], - )?; + let target_name: Bound<'py, PyAny> = resolve_target_name( + py, + &native_field(&discovered, "project_config")?, + &native_field(&discovered, "local_config")?, + &selected_target.clone(), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; let defer_clone_from: Bound<'py, PyAny> = or_none(native_field(request, "defer_clone_from")?, || { if target_name.is_none() { @@ -201,12 +195,9 @@ pub(crate) fn resolve_build_context<'py>( let load_sources: Bound<'py, PyAny> = native_field(request, "load_sources")?; let should_load_sources: bool = native_field(request, "reload_sources")?.is_truthy()? || if load_sources.is_none() { - call_with( - ®istry::native(py, NativeAttr::BuildEffectiveSettingsConfig)?, - &[("discovered_inputs", discovered.clone())], - )? - .getattr("auto_load_sources")? - .is_truthy()? + build_effective_settings_config(&discovered.clone())? + .getattr("auto_load_sources")? + .is_truthy()? } else { load_sources.is_truthy()? }; 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 b23402a663..6bb425f572 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/lifecycle.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/lifecycle.rs @@ -14,14 +14,17 @@ use sqlbuild_core::cli_text::models::CliStyle; use crate::bindings::_helpers::build_command::support::{ boolean, emit, frozenset, or_none, text, tuple, }; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; +use crate::bindings::_helpers::compile_driver::relation_targets::build_project_relation_targets; +use crate::bindings::_helpers::compile_driver::relation_targets::build_python_relation_targets; +use crate::bindings::_helpers::graph::python_lifecycle::build_python_sql_run_lifecycle; 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::planner_scope::loaders::build_intermediate_source_map; use crate::bindings::_helpers::records::construct::{enum_member, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::ids::RecordId; const SUCCESS_STATUS: &str = "success"; @@ -319,10 +322,7 @@ fn lifecycle_plan<'py>( ), ], )?; - let plan: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::BuildPythonSqlRunLifecycle)?, - &[("selection", selection), ("python_graph", graph.clone())], - )?; + let plan: Bound<'py, PyAny> = build_python_sql_run_lifecycle(&selection, &graph.clone())?; let source_type: Bound<'py, PyAny> = enum_member(py, RecordId::CompiledResourceType, "SOURCE")?; let mut keys: Vec> = Vec::new(); for name in native_field(&plan, "ingress_loader_names")?.try_iter()? { @@ -336,13 +336,11 @@ fn lifecycle_plan<'py>( imported(py, "builtins", "dict")?.call1((native_field(&plan_output, "source_map")?,))?; source_map.call_method1( "update", - (call_with( - ®istry::native(py, NativeAttr::BuildIntermediateSourceMap)?, - &[ - ("project", native_field(pipeline, "project")?), - ("selected_keys", frozenset(py, &keys)?), - ], - )?,), + (build_intermediate_source_map( + &native_field(pipeline, "project")?, + &frozenset(py, &keys)?, + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?,), )?; Ok(LifecyclePlan { graph, @@ -404,22 +402,30 @@ pub(crate) fn prepare_direct_python_lifecycle<'py>( )?, )], )?; - let relation_targets: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::BuildPythonRelationTargets)?, - &[ - ("adapter", adapter.clone()), - ("project", project.clone()), - ("plan_output", plan_output.clone()), - ("required_refs", required), - ], - )?; - let project_relations: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::BuildProjectRelationTargets)?, - &[ - ("adapter", adapter.clone()), - ("plan_output", plan_output.clone()), - ], - )?; + let relation_targets: Bound<'py, PyAny> = build_python_relation_targets( + py, + Some(keywords( + py, + &[ + ("adapter", adapter.clone()), + ("project", project.clone()), + ("plan_output", plan_output.clone()), + ("required_refs", required), + ], + )?), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; + let project_relations: Bound<'py, PyAny> = build_project_relation_targets( + py, + Some(keywords( + py, + &[ + ("adapter", adapter.clone()), + ("plan_output", plan_output.clone()), + ], + )?), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; let parts: RuntimeParts<'_, 'py> = RuntimeParts { adapter: &adapter, connection_config: &connection_config, 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 5ec04db0ed..c1f66a26c1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/planning.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/planning.rs @@ -14,9 +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; +use crate::bindings::_helpers::compile_driver::pipeline::run_compile_pipeline; +use crate::bindings::_helpers::compile_driver::python_calls::{call_with, keywords}; +use crate::bindings::_helpers::manifest::builder::build_manifest; use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::plan_output::plan_text::format_plan; use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; @@ -111,14 +114,17 @@ pub(crate) fn compile_build_plan<'py>(context: &BuildContext<'py>) -> PyResult = py.import("sys")? .getattr(if machine { "stderr" } else { "stdout" })?; - let plan_text: String = call_with( - ®istry::native(py, NativeAttr::FormatPlan)?, - &[ - ("plan", native_field(pipeline, "plan_output")?), - ( - "full_refresh", - native_field(&context.request, "full_refresh")?, - ), - ("use_color", boolean(py, context.use_color)), - ( - "python_plan_entries", - native_field(pipeline, "python_plan_entries")?, - ), - ("include_direct_freshness_diagnostics", boolean(py, false)), - ], - )? + let plan_text: String = format_plan( + py, + Some(keywords( + py, + &[ + ("plan", native_field(pipeline, "plan_output")?), + ( + "full_refresh", + native_field(&context.request, "full_refresh")?, + ), + ("use_color", boolean(py, context.use_color)), + ( + "python_plan_entries", + native_field(pipeline, "python_plan_entries")?, + ), + ("include_direct_freshness_diagnostics", boolean(py, false)), + ], + )?), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))? .extract()?; emit(&stream, &format!("\n{plan_text}\n\n")) } @@ -199,8 +209,8 @@ pub(crate) fn write_compile_target( let py: Python<'_> = context.py(); let plan: Bound<'_, PyAny> = native_field(pipeline, "plan_output")?; let manifest: Bound<'_, PyAny> = if context.request_flag("manifest")? { - call_with( - ®istry::native(py, NativeAttr::BuildManifest)?, + build_manifest(Some(&keywords( + py, &[ ("project", native_field(pipeline, "project")?), ("plan_output", plan.clone()), @@ -215,21 +225,25 @@ pub(crate) fn write_compile_target( ("upstream_deps", native_field(&plan, "upstream_deps")?), ("downstream_deps", native_field(&plan, "downstream_deps")?), ], - )? + )?)) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))? } else { none(py) }; - call_with( - ®istry::native(py, NativeAttr::WriteCompileTarget)?, - &[ - ( - "target_dir", - joined(&context.get("effective_project_dir")?, &["target"])?, - ), - ("adapter", context.get("adapter")?), - ("plan_output", plan), - ("manifest", manifest), - ], + crate::bindings::_helpers::compile_command::target_writer::write_compile_target( + py, + Some(keywords( + py, + &[ + ( + "target_dir", + joined(&context.get("effective_project_dir")?, &["target"])?, + ), + ("adapter", context.get("adapter")?), + ("plan_output", plan), + ("manifest", manifest), + ], + )?), )?; Ok(()) } 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 393b729b8a..44dca30ee9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/policies.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/policies.rs @@ -240,7 +240,7 @@ pub(crate) fn enforce_plan_policies( /// Apply every plan safety gate before a build executes anything. #[pyfunction] #[pyo3(name = "enforce_plan_safety_policies", signature = (**options))] -fn enforce_plan_safety_policies_binding<'py>( +pub(crate) fn enforce_plan_safety_policies_binding<'py>( py: Python<'py>, options: Option>, ) -> Result<(), PyErr> { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/build_progress/footer.rs b/crates/sqlbuild-python/src/bindings/_helpers/build_progress/footer.rs index 15b9afca2f..72ba57bd60 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_progress/footer.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_progress/footer.rs @@ -11,7 +11,7 @@ use crate::bindings::_helpers::build_progress::result_text::{build_text, python_ /// Render the build completion line, warnings, failures and skipped rollup. #[pyfunction] #[pyo3(signature = (*, result, elapsed, use_color, python_node_results = None))] -fn format_build_footer( +pub(crate) fn format_build_footer( result: &Bound<'_, PyAny>, elapsed: f64, use_color: bool, 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 c1a1622a0c..ea94881054 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/check/check_results.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/check/check_results.rs @@ -197,7 +197,10 @@ pub(crate) fn results_json(results: &[Bound<'_, PyAny>]) -> PyResult { /// Write human-readable check result rows. #[pyfunction] #[pyo3(signature = (**options))] -fn write_check_results(py: Python<'_>, options: Option>) -> PyResult<()> { +pub(crate) fn write_check_results( + py: Python<'_>, + options: Option>, +) -> PyResult<()> { let arguments: KeywordArguments<'_> = KeywordArguments::read( py, "write_check_results", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cli/dispatch.rs b/crates/sqlbuild-python/src/bindings/_helpers/cli/dispatch.rs index 8b1f0900e8..5da47cec7a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cli/dispatch.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cli/dispatch.rs @@ -4,6 +4,7 @@ use std::path::PathBuf; use crate::bindings::_helpers::cli::launch_state::withhold_launch_record; use crate::bindings::_helpers::command_runtime::warehouse_scope::command_warehouse_scope; +use crate::bindings::_helpers::command_runtime::warehouses::validate_cli_warehouse; use crate::bindings::_helpers::diff_report::support::cli_error; use crate::bindings::_helpers::records::construct::{new_record_with, record_class}; use pyo3::IntoPyObjectExt; @@ -19,7 +20,7 @@ use sqlbuild_cli::dispatch::models::{ use sqlbuild_core::format_command::main::format_path_selectors::format_path_selectors; 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::compile_driver::python_calls::{imported, within}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; @@ -346,13 +347,12 @@ pub(crate) fn dispatch_command<'py>( .call1((raw_project_dir,))? .call_method0("resolve")? }; - call_with( - ®istry::native(py, NativeAttr::ValidateCliWarehouse)?, - &[ - ("cli_warehouse", warehouse.clone()), - ("project_dir", project_dir), - ], - )?; + validate_cli_warehouse( + py, + warehouse.clone().extract::>()?, + project_dir, + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; } let scope: Bound<'py, PyAny> = Bound::new( py, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cli/entry.rs b/crates/sqlbuild-python/src/bindings/_helpers/cli/entry.rs index 6f4eb630b7..9dfe4824df 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cli/entry.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cli/entry.rs @@ -16,6 +16,7 @@ use crate::bindings::_helpers::cli::argv::parse_cli_invocation; 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_command::report::format_compile_error_json; 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; @@ -26,6 +27,7 @@ use crate::bindings::_helpers::records::construct::record_class; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::workspace_commands::commands::maintain_sqlbuild_skills; 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; @@ -170,15 +172,13 @@ fn write_compile_machine_error( } else { py.None().into_bound(py) }; - let document: Bound<'_, PyAny> = call_with( - ®istry::native(py, NativeAttr::FormatCompileErrorJson)?, - &[ - ("code", code), - ("message", message), - ("help_text", help), - ("location", location), - ], - )?; + let document: Bound<'_, PyAny> = format_compile_error_json( + code, + message, + help, + Some(location).filter(|value| !value.is_none()), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; print_object(&document, None, None) } @@ -353,22 +353,20 @@ 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( - ®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(®istry::error(py, ErrorId::CliUser)?)? - || value.is_instance_of::() - || value.is_instance_of::() - { - return Ok(()); + let result: Bound<'_, PyAny> = + match maintain_sqlbuild_skills(py, project_dir.extract::()?) { + Ok(result) => result, + Err(error) => { + let value: Bound<'_, PyAny> = error.value(py).clone().into_any(); + if value.is_instance(®istry::error(py, ErrorId::CliUser)?)? + || value.is_instance_of::() + || value.is_instance_of::() + { + return Ok(()); + } + return Err(error); } - return Err(error); - } - }; + }; let message: Bound<'_, PyAny> = native_field(&result, "message")?; if message.is_truthy()? { print_object(&message, Some("stderr"), Some(""))?; 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 b40b095611..29c1214b2f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cli/progress_text.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cli/progress_text.rs @@ -29,7 +29,11 @@ const HEADER_OPTIONS: (&[&str], &[&str]) = ( /// Format command and execution context for a progress header. #[pyfunction] #[pyo3(signature = (*, command, target, concurrency))] -fn format_execution_header(command: &str, target: Option<&str>, concurrency: i64) -> String { +pub(crate) fn format_execution_header( + command: &str, + target: Option<&str>, + concurrency: i64, +) -> String { format_header(command, target, concurrency) } 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 d266fa5af8..476da49558 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_command.rs @@ -24,16 +24,22 @@ 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::command_runtime::connection::resolve_target_connection_config; use crate::bindings::_helpers::compile_driver::discovery::discover_project_inputs; use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, within}; +use crate::bindings::_helpers::contracts::spec_resolution::resolve_target_config; +use crate::bindings::_helpers::contracts::spec_resolution::resolve_target_name; use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::planner_selection::unit_tests::reject_unit_test_selectors; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::result_output::documents::format_clone_execution_json; +use crate::bindings::_helpers::result_output::documents::write_execution_json_output; const NO_DESTINATION: (&str, &str) = ( "C406", @@ -107,16 +113,11 @@ 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( - ®istry::native(py, NativeAttr::ResolveTargetConfig)?, - &[ - ( - "project_config", - native_field(discovered, "project_config")?, - ), - ("local_config", native_field(discovered, "local_config")?), - ("target_name", text(py, target)), - ], + resolve_target_config( + py, + &native_field(discovered, "project_config")?, + &native_field(discovered, "local_config")?, + &text(py, target), )? .getattr("clone")? .getattr(flag)? @@ -140,27 +141,20 @@ fn invocation<'py>(py: Python<'py>, request: &Bound<'py, PyAny>) -> PyResult = native_field(request, "destination_target_name")?; if destination.is_none() { - destination = call_with( - ®istry::native(py, NativeAttr::ResolveTargetName)?, - &[ - ( - "project_config", - native_field(&discovered, "project_config")?, - ), - ("local_config", native_field(&discovered, "local_config")?), - ("selected_target", py.None().into_bound(py)), - ], - )?; + destination = resolve_target_name( + py, + &native_field(&discovered, "project_config")?, + &native_field(&discovered, "local_config")?, + &py.None().into_bound(py), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; } if destination.is_none() { return Err(user_error(py, NO_DESTINATION)); @@ -203,14 +197,17 @@ fn connect<'py>( invocation: &Invocation<'py>, ) -> PyResult<(Bound<'py, PyAny>, Bound<'py, PyAny>)> { let py: Python<'py> = request.py(); - let config: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::ResolveTargetConnectionConfig)?, - &[ - ("discovered_inputs", invocation.discovered.clone()), - ("project_dir", invocation.project_dir.clone()), - ("target_name", text(py, &invocation.destination)), - ("cli_vars", native_field(request, "cli_vars")?), - ], + let config: Bound<'py, PyAny> = resolve_target_connection_config( + py, + Some(keywords( + py, + &[ + ("discovered_inputs", invocation.discovered.clone()), + ("project_dir", invocation.project_dir.clone()), + ("target_name", text(py, &invocation.destination)), + ("cli_vars", native_field(request, "cli_vars")?), + ], + )?), )?; let progress: Bound<'py, PyAny> = progress_reporter(&invocation.stream, invocation.style)?; progress.call_method1( @@ -350,24 +347,19 @@ fn run_clone_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult = call_with( - ®istry::native(py, NativeAttr::FormatCloneExecutionJson)?, - &[ - ("result", result.clone()), - ("resource_types_by_name", resource_types.into_any()), - ], - )?; - call_with( - ®istry::native(py, NativeAttr::WriteExecutionJsonOutput)?, - &[ - ("payload", payload), - ("json_output", PyBool::new(py, false).to_owned().into_any()), - ( - "json_output_path", - native_field(request, "json_output_path")?, - ), - ], - )?; + let payload: Bound<'_, PyAny> = + format_clone_execution_json(py, result.clone(), resource_types.into_any()) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; + write_execution_json_output( + py, + payload, + PyBool::new(py, false) + .to_owned() + .into_any() + .extract::()?, + native_field(request, "json_output_path")?, + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; Ok(clone_exit_code(&items)) } 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 d03768ef96..8ca2f90f05 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_execution.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_execution.rs @@ -13,6 +13,7 @@ use crate::bindings::_helpers::clone::clone_progress::{ CloneFingerprintReporter, CloneItemPrinter, }; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, within}; +use crate::bindings::_helpers::contracts::spec_resolution::resolve_target_config; use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::records::construct::{enum_member, new_record, new_record_with}; @@ -156,22 +157,11 @@ fn dependency_locations<'py>( fn target_config<'py>(invocation: &Invocation<'py>) -> PyResult> { let py: Python<'py> = invocation.adapter.py(); - call_with( - ®istry::native(py, NativeAttr::ResolveTargetConfig)?, - &[ - ( - "project_config", - native_field(&invocation.discovered, "project_config")?, - ), - ( - "local_config", - native_field(&invocation.discovered, "local_config")?, - ), - ( - "target_name", - PyString::new(py, &invocation.destination).into_any(), - ), - ], + resolve_target_config( + py, + &native_field(&invocation.discovered, "project_config")?, + &native_field(&invocation.discovered, "local_config")?, + &PyString::new(py, &invocation.destination).into_any(), ) } 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 8ea9db72dd..cfe0bcd61d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_pipeline.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_pipeline.rs @@ -6,13 +6,12 @@ use pyo3::{pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::boundary::keyword_request::Request; use crate::bindings::_helpers::compile_driver::project::build_compiled_project; -use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::contracts::spec_resolution::resolve_target_config; +use crate::bindings::_helpers::planner_entries::clone_plans::run_clone_planning; use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::ids::RecordId; /// The `ClonePipelineResult` fields after the two projects, in `run_clone_planning` order. const PLANNED_FIELDS: [&str; 8] = [ @@ -118,19 +117,23 @@ pub(crate) fn prepared_clone_pipeline<'py>( source_project(pipeline, &origin_project, origin_target)?; let destination_source_project: Bound<'py, PyAny> = source_project(pipeline, &destination_project, destination_target)?; - let planned: Bound<'py, PyTuple> = call_with( - ®istry::native(py, NativeAttr::RunClonePlanning)?, - &[ - ("project", destination_project.clone()), - ("select", native_field(&pipeline.options, "select")?), - ("exclude", native_field(&pipeline.options, "exclude")?), - ("adapter", pipeline.adapter.clone()), - ("connection", native_field(&pipeline.connection, "handle")?), - ("origin_project", origin_project.clone()), - ("origin_source_project", origin_source_project), - ("destination_source_project", destination_source_project), - ], - )? + let planned: Bound<'py, PyTuple> = run_clone_planning( + py, + Some(keywords( + py, + &[ + ("project", destination_project.clone()), + ("select", native_field(&pipeline.options, "select")?), + ("exclude", native_field(&pipeline.options, "exclude")?), + ("adapter", pipeline.adapter.clone()), + ("connection", native_field(&pipeline.connection, "handle")?), + ("origin_project", origin_project.clone()), + ("origin_source_project", origin_source_project), + ("destination_source_project", destination_source_project), + ], + )?), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))? .downcast_into()?; let mut fields: Vec<(&str, Bound<'py, PyAny>)> = vec![ ("origin_project", origin_project), 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 f2b6c612c4..dd60b72550 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/connection.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/connection.rs @@ -15,9 +15,12 @@ use sqlbuild_core::constants::PROJECT_CONFIG_FILENAME; use crate::bindings::_helpers::command_runtime::runtime_errors::raised; use crate::bindings::_helpers::command_runtime::warehouses::{WarehouseInputs, apply_warehouse}; +use crate::bindings::_helpers::compile_attachment::effective_config::build_effective_connection_config; use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, }; +use crate::bindings::_helpers::contracts::spec_resolution::resolve_target_config; +use crate::bindings::_helpers::contracts::spec_resolution::resolve_target_name; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::native_host::third_party::ThirdPartyHost; use crate::bindings::_helpers::records::fields::field as native_field; @@ -206,14 +209,13 @@ fn require_configured_connection( { return Ok(()); } - let target_name: Bound<'_, PyAny> = call_with( - ®istry::native(py, NativeAttr::ResolveTargetName)?, - &[ - ("project_config", project_config.clone()), - ("local_config", local_config.clone()), - ("selected_target", selected_target.clone()), - ], - )?; + let target_name: Bound<'_, PyAny> = resolve_target_name( + py, + &project_config.clone(), + &local_config.clone(), + &selected_target.clone(), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; let project_file: String = path(py, project_dir)? .div(PROJECT_CONFIG_FILENAME)? .str()? @@ -221,14 +223,8 @@ fn require_configured_connection( if target_name.is_none() { return Err(raised(py, missing_connection_error(&project_file, None))); } - let target_config: Bound<'_, PyAny> = call_with( - ®istry::native(py, NativeAttr::ResolveTargetConfig)?, - &[ - ("project_config", project_config), - ("local_config", local_config), - ("target_name", target_name.clone()), - ], - )?; + let target_config: Bound<'_, PyAny> = + resolve_target_config(py, &project_config, &local_config, &target_name.clone())?; if !native_field(&target_config, "connection_name")?.is_none() || native_field(&target_config, "connection")?.is_truthy()? { @@ -250,13 +246,10 @@ pub(crate) fn project_connection_config<'py>( if require_connection { require_configured_connection(discovered_inputs, project_dir, selected_target)?; } - let raw_config: Bound<'py, PyDict> = call_with( - ®istry::native(py, NativeAttr::BuildEffectiveConnectionConfig)?, - &[ - ("discovered_inputs", discovered_inputs.clone()), - ("selected_target", selected_target.clone()), - ("cli_vars", cli_vars.clone()), - ], + let raw_config: Bound<'py, PyDict> = build_effective_connection_config( + &discovered_inputs.clone(), + Some(selected_target.clone()).filter(|value| !value.is_none()), + Some(cli_vars.clone()).filter(|value| !value.is_none()), )? .cast_into()?; let adapter_name: Bound<'py, PyAny> = call_with( @@ -308,7 +301,7 @@ pub(crate) fn resolve_project_connection_config<'py>( /// Resolve the effective connection config for one named target. #[pyfunction] #[pyo3(signature = (**options))] -fn resolve_target_connection_config<'py>( +pub(crate) fn resolve_target_connection_config<'py>( py: Python<'py>, options: Option>, ) -> PyResult> { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/warehouses.rs b/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/warehouses.rs index 752a85c349..31feb1c9b8 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/warehouses.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/warehouses.rs @@ -22,7 +22,9 @@ use crate::bindings::_helpers::command_runtime::runtime_errors::{ expected_resolution_error, raised, }; use crate::bindings::_helpers::command_runtime::warehouse_scope::current_command_warehouse_scope; +use crate::bindings::_helpers::compile_attachment::effective_config::build_effective_runtime_config; use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::contracts::spec_resolution::resolve_target_name; use crate::bindings::_helpers::discovery::configuration::discover_project_configuration; use crate::bindings::_helpers::model_config::template_data::expanded_template_data; use crate::bindings::_helpers::native_host::adapter::AdapterHost; @@ -140,14 +142,13 @@ pub(crate) fn find_authored<'py>( let project_config: Bound<'py, PyAny> = native_field(inputs.discovered_inputs, "project_config")?; let local_config: Bound<'py, PyAny> = native_field(inputs.discovered_inputs, "local_config")?; - let target_name: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::ResolveTargetName)?, - &[ - ("project_config", project_config.clone()), - ("local_config", local_config.clone()), - ("selected_target", inputs.selected_target.clone()), - ], - )?; + let target_name: Bound<'py, PyAny> = resolve_target_name( + py, + &project_config.clone(), + &local_config.clone(), + &inputs.selected_target.clone(), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; if target_name.is_none() { return Ok(None); } @@ -177,14 +178,12 @@ fn expand_authored( cli_vars: &Bound<'_, PyAny>, ) -> PyResult { let py: Python<'_> = found.group.py(); - let runtime: Bound<'_, PyAny> = call_with( - ®istry::native(py, NativeAttr::BuildEffectiveRuntimeConfig)?, - &[ - ("discovered_inputs", inputs.discovered_inputs.clone()), - ("selected_target", inputs.selected_target.clone()), - ("cli_vars", cli_vars.clone()), - ], - )?; + let runtime: Bound<'_, PyAny> = build_effective_runtime_config( + &inputs.discovered_inputs.clone(), + Some(inputs.selected_target.clone()).filter(|value| !value.is_none()), + Some(cli_vars.clone()).filter(|value| !value.is_none()), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; let expanded: Bound<'_, PyAny> = expanded_template_data( PyString::new(py, &found.authored.warehouse).as_any(), (runtime.get_item(1)?.downcast()?, &PyDict::new(py)), @@ -278,7 +277,7 @@ fn optional_inputs<'a, 'py>( /// Reject an empty, malformed, or unsupported `--warehouse` before the command runs. #[pyfunction] #[pyo3(signature = (*, cli_warehouse, project_dir))] -fn validate_cli_warehouse( +pub(crate) fn validate_cli_warehouse( py: Python<'_>, cli_warehouse: Option, project_dir: Bound<'_, PyAny>, @@ -365,7 +364,7 @@ pub(crate) fn apply_warehouse<'py>( /// Return connection config whose warehouse follows the active command's group. #[pyfunction] #[pyo3(signature = (**options))] -fn apply_command_warehouse<'py>( +pub(crate) fn apply_command_warehouse<'py>( py: Python<'py>, options: Option>, ) -> PyResult> { @@ -386,7 +385,7 @@ fn apply_command_warehouse<'py>( /// Describe the warehouse an already resolved connection uses and where it came from. #[pyfunction] #[pyo3(signature = (*, discovered_inputs, selected_target, connection_config))] -fn describe_connection_warehouse<'py>( +pub(crate) fn describe_connection_warehouse<'py>( py: Python<'py>, discovered_inputs: Bound<'py, PyAny>, selected_target: Bound<'py, PyAny>, From 44f66df3d7cd85f0e3dd4a2021daf8fccfcdd10f Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Sat, 17 Oct 2026 01:57:46 +0100 Subject: [PATCH 20/28] refactor: call native entry points directly in compile_command to cost_report bindings Replace keyword calls through sqlbuild._native attributes with direct Rust calls to the pyfunctions. Entry points that tests replace on the module are left for a follow-up test rewrite. --- .../_helpers/compile_command/target_writer.rs | 2 +- .../_helpers/compile_driver/deferral.rs | 2 +- .../compile_driver/pipeline_entries.rs | 2 +- .../_helpers/contract_command/command.rs | 43 ++++++------ .../_helpers/contract_command/write.rs | 52 ++++++++------- .../_helpers/contracts/spec_resolution.rs | 2 +- .../bindings/_helpers/cost_command/command.rs | 65 ++++++++++--------- .../bindings/_helpers/cost_command/refresh.rs | 33 +++++----- .../_helpers/cost_report/functions.rs | 20 ++++-- 9 files changed, 117 insertions(+), 104 deletions(-) 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 3db67b4ed0..83ad0fd0f1 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 @@ -732,7 +732,7 @@ fn write_static_compile_target<'py>( /// Write the planner's compiled output files under `target_dir`. #[pyfunction] #[pyo3(signature = (**options))] -fn write_compile_target<'py>( +pub(crate) fn write_compile_target<'py>( py: Python<'py>, options: Option>, ) -> PyResult> { 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 6cfb4c06df..11a6cc5dc5 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/deferral.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/deferral.rs @@ -292,7 +292,7 @@ fn deferred_relations<'py>( /// Resolve native target locations and warehouse relations for model deferral. #[pyfunction] #[pyo3(signature = (**options))] -fn resolve_model_deferral_inputs<'py>( +pub(crate) fn resolve_model_deferral_inputs<'py>( py: Python<'py>, options: Option>, ) -> PyResult> { 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 5c386ea826..0228b0fdb5 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 @@ -121,7 +121,7 @@ pub(crate) fn project_graph<'py>(project: &Bound<'py, PyAny>) -> PyResult( +pub(crate) fn plan_has_executable_work<'py>( py: Python<'py>, options: Option>, ) -> PyResult { 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 8705569b8b..1d584b3daf 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/contract_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/contract_command/command.rs @@ -21,15 +21,19 @@ use sqlbuild_core::contract_command::models::{ 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::discovery::discover_project_inputs; +use crate::bindings::_helpers::compile_driver::pipeline_entries::build_project_graph_from_compiled_project; use crate::bindings::_helpers::compile_driver::project::build_compiled_project; use crate::bindings::_helpers::compile_driver::python_calls::keywords; 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::planner_selection::selectors::select_project_graph; +use crate::bindings::_helpers::planner_selection::unit_tests::reject_unit_test_selectors; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::refactor::contract_compare::compare_contracts; const GENERATE_ACTION: &str = "generate"; @@ -102,18 +106,16 @@ fn select_resources<'py>( ], )?), )?; - let graph: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::BuildProjectGraphFromCompiledProject)?, - &[("project", project)], - )?; - let selected: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::SelectProjectGraph)?, - &[ - ("graph", graph.clone()), - ("select", native_field(request, "select")?), - ("exclude", native_field(request, "exclude")?), - ], + let graph: Bound<'py, PyAny> = build_project_graph_from_compiled_project( + py, + Some(keywords(py, &[("project", project)])?), )?; + let selected: Bound<'py, PyAny> = select_project_graph( + &graph.clone(), + &native_field(request, "select")?, + &native_field(request, "exclude")?, + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; let compiled: Bound<'py, PyAny> = native_field(&graph, "project")?; let models: Bound<'py, PyTuple> = selected_of(native_field(&compiled, "models")?, &selected)?; let sources: Bound<'py, PyTuple> = selected_of(native_field(&compiled, "sources")?, &selected)?; @@ -209,15 +211,17 @@ fn compare<'py>( }, ),), )?; - let evidence: Bound<'py, PyAny> = match call_with( - ®istry::native(py, NativeAttr::CompareContracts)?, + let evidence: Bound<'py, PyAny> = match compare_contracts(Some(&keywords( + py, &[ ("adapter", adapter.clone()), ("connection", connection.clone()), ("models", selection.models.clone().into_any()), ("sources", selection.sources.clone().into_any()), ], - ) { + )?)) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py)) + { Ok(evidence) => evidence, Err(error) => { if exception(py, &error) { @@ -378,13 +382,10 @@ fn run_contract_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult< py, Some(keywords(py, &[("project_dir", project_dir.clone())])?), )?; - call_with( - ®istry::native(py, NativeAttr::RejectUnitTestSelectors)?, - &[ - ("select", native_field(request, "select")?), - ("exclude", native_field(request, "exclude")?), - ("discovered_inputs", discovered.clone()), - ], + reject_unit_test_selectors( + &native_field(request, "select")?, + &native_field(request, "exclude")?, + &discovered.clone(), )?; let from_target: Bound<'_, PyAny> = native_field(request, "from_target")?; if !discovered 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 382fd74e47..94c63346a4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/contract_command/write.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/contract_command/write.rs @@ -12,6 +12,9 @@ use crate::bindings::_helpers::records::construct::{enum_member, new_record}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::refactor::contract_edits::edit_contract_model; +use crate::bindings::_helpers::refactor::contract_edits::edit_contract_source; +use crate::bindings::_helpers::refactor::contract_edits::remaining_contract_findings; const UTF8: &str = "utf-8"; @@ -49,14 +52,13 @@ pub(crate) fn write_contracts<'py>( let (path, edited): (Bound<'py, PyAny>, Bound<'py, PyAny>) = if native_field(&evidence, "resource_type")?.eq(&model_type)? { let model: Bound<'py, PyAny> = models.as_any().get_item(&name)?; - let edited: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::EditContractModel)?, - &[ - ("model", model.clone()), - ("physical_columns", physical_columns), - ("overwrite", write.overwrite.clone()), - ], - )?; + let edited: Bound<'py, PyAny> = edit_contract_model( + py, + &model.clone(), + &physical_columns, + write.overwrite.clone().extract::()?, + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; ( write .project_dir @@ -67,29 +69,29 @@ pub(crate) fn write_contracts<'py>( let source: Bound<'py, PyAny> = sources.as_any().get_item(&name)?; let path: Bound<'py, PyAny> = native_field(&source, "source_file")?.getattr("file_path")?; - let edited: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::EditContractSource)?, - &[ - ("source", source), - ("physical_columns", physical_columns), - ("overwrite", write.overwrite.clone()), - ("contents", updates.as_any().call_method1("get", (&path,))?), - ], - )?; + let edited: Bound<'py, PyAny> = edit_contract_source( + &source, + &physical_columns, + write.overwrite.clone().extract::()?, + updates + .as_any() + .call_method1("get", (&path,))? + .extract::>()?, + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; (path, edited) }; let (contents, conflict): (Bound<'py, PyAny>, Bound<'py, PyAny>) = edited.extract()?; if !contents.is_none() && contents.ne(read_text(&path)?)? { updates.set_item(&path, contents)?; } - let remaining: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::RemainingContractFindings)?, - &[ - ("findings", findings), - ("overwrite", write.overwrite.clone()), - ("conflict", conflict), - ], - )?; + let remaining: Bound<'py, PyAny> = remaining_contract_findings( + py, + &findings, + write.overwrite.clone().extract::()?, + conflict.extract::>()?, + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; updated_evidence.append(replaced(&evidence, &[("findings", remaining)])?)?; } let evidence: Bound<'py, PyAny> = PyTuple::new(py, updated_evidence)?.into_any(); 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 5e0cd028fd..be8ba96914 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/contracts/spec_resolution.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/contracts/spec_resolution.rs @@ -234,7 +234,7 @@ fn blank(text: &str) -> bool { /// Parse a one-, two-, or three-part loader destination. #[pyfunction] #[pyo3(signature = (*, destination, default_database, default_schema))] -fn loader_destination_parts<'py>( +pub(crate) fn loader_destination_parts<'py>( py: Python<'py>, destination: &str, default_database: Option>, 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 f2955d695b..c650ce4d99 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_command/command.rs @@ -12,11 +12,15 @@ use sqlbuild_core::cost_command::main::cost_history_limit::cost_history_limit; use sqlbuild_core::cost_command::main::select_cost_run::select_cost_run; use sqlbuild_core::cost_command::main::validate_cost_request::validate_cost_request; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::cost_command::bounds::{parse_bound, parse_since}; 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::cost_report::functions::format_cost_history; +use crate::bindings::_helpers::cost_report::functions::format_cost_history_json; +use crate::bindings::_helpers::cost_report::functions::list_cost_runs; +use crate::bindings::_helpers::cost_report::functions::resolve_cost_run; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::NativeAttr; @@ -102,9 +106,12 @@ 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( - ®istry::native(py, NativeAttr::ListCostRuns)?, - &[("project_dir", request.project_dir.clone())], + list_cost_runs( + py, + request + .project_dir + .clone() + .extract::()?, )? .try_iter()? .collect() @@ -177,26 +184,20 @@ 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( - ®istry::native(py, NativeAttr::FormatCostHistoryJson)?, - &[ - ("records", records.clone()), - ("matching_count", count.clone()), - ], - )? - .extract()?; + let payload: String = + format_cost_history_json(&records.clone(), count.clone().extract::>()?) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))? + .extract()?; request.emit(&payload, || { - call_with( - ®istry::native(py, NativeAttr::FormatCostHistory)?, - &[ - ("records", records), - ("matching_count", count), - ( - "use_color", - PyBool::new(py, request.use_color()?).to_owned().into_any(), - ), - ], - )? + format_cost_history( + &records, + count.extract::()?, + PyBool::new(py, request.use_color()?) + .to_owned() + .into_any() + .extract::()?, + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))? .extract() })?; Ok(0) @@ -207,13 +208,17 @@ fn resolve_run<'py>(request: &CostRequest<'py>) -> PyResult> { let py: Python<'py> = request.request.py(); let records: Vec> = cost_runs(request)?; if request.selector == LATEST_SELECTOR { - let latest: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::ResolveCostRun)?, - &[ - ("project_dir", request.project_dir.clone()), - ("selector", PyString::new(py, &request.selector).into_any()), - ], - )?; + let latest: Bound<'py, PyAny> = resolve_cost_run( + py, + request + .project_dir + .clone() + .extract::()?, + &PyString::new(py, &request.selector) + .into_any() + .extract::()?, + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; if latest.is_none() { return Err(cost_error( py, 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 6911b73225..9b552fc302 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_command/refresh.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_command/refresh.rs @@ -13,6 +13,9 @@ use crate::bindings::_helpers::command_runtime::adapters::resolve_cli_adapter; use crate::bindings::_helpers::compile_driver::discovery::discover_project_inputs; use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::cost_report::functions::read_cost_run; +use crate::bindings::_helpers::cost_report::functions::resolve_cost_run; +use crate::bindings::_helpers::cost_report::functions::write_cost_run; use crate::bindings::_helpers::cost_telemetry::values::cost_aware; use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; @@ -43,24 +46,22 @@ fn refresh_record<'py>( ) -> PyResult> { let py: Python<'py> = project_dir.py(); let resolve = || { - call_with( - ®istry::native(py, NativeAttr::ResolveCostRun)?, - &[ - ("project_dir", project_dir.clone()), - ("selector", selector.clone()), - ], + resolve_cost_run( + py, + project_dir.clone().extract::()?, + &selector.clone().extract::()?, ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py)) }; if selector.extract::()? == LATEST_SELECTOR { return resolve(); } - let exact: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::ReadCostRun)?, - &[ - ("project_dir", project_dir.clone()), - ("run_id", selector.clone()), - ], - )?; + let exact: Bound<'py, PyAny> = read_cost_run( + py, + project_dir.clone().extract::()?, + &selector.clone().extract::()?, + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; if !exact.is_none() { return Ok(exact); } @@ -179,10 +180,8 @@ fn refresh(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult<()> { changes.set_item("cost", summary)?; let refreshed: Bound<'_, PyAny> = imported(py, "dataclasses", "replace")?.call((record,), Some(&changes))?; - call_with( - ®istry::native(py, NativeAttr::WriteCostRun)?, - &[("project_dir", project_dir), ("record", refreshed)], - )?; + write_cost_run(py, project_dir.extract::()?, &refreshed) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; Ok(()) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cost_report/functions.rs b/crates/sqlbuild-python/src/bindings/_helpers/cost_report/functions.rs index 3e818f3b23..973cb5808d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_report/functions.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_report/functions.rs @@ -36,7 +36,7 @@ fn optional_record<'py>( /// Atomically persist one run's cost artifact and return its path. #[pyfunction] #[pyo3(signature = (*, project_dir, record))] -fn write_cost_run( +pub(crate) fn write_cost_run( py: Python<'_>, project_dir: PathBuf, record: &Bound<'_, PyAny>, @@ -47,7 +47,7 @@ fn write_cost_run( /// Read the run stored under an exact run ID, or `None`. #[pyfunction] #[pyo3(signature = (*, project_dir, run_id))] -fn read_cost_run<'py>( +pub(crate) fn read_cost_run<'py>( py: Python<'py>, project_dir: PathBuf, run_id: &str, @@ -59,14 +59,17 @@ fn read_cost_run<'py>( /// Every readable persisted run, newest first. #[pyfunction] #[pyo3(signature = (*, project_dir))] -fn list_cost_runs<'py>(py: Python<'py>, project_dir: PathBuf) -> PyResult> { +pub(crate) fn list_cost_runs<'py>( + py: Python<'py>, + project_dir: PathBuf, +) -> PyResult> { Ok(record_tuple(py, &store::list(py, &project_dir)?)?.into_any()) } /// The latest finished run, or the only run whose ID starts with the selector. #[pyfunction] #[pyo3(signature = (*, project_dir, selector))] -fn resolve_cost_run<'py>( +pub(crate) fn resolve_cost_run<'py>( py: Python<'py>, project_dir: PathBuf, selector: &str, @@ -84,7 +87,7 @@ fn format_cost_json(record: &Bound<'_, PyAny>) -> PyResult { /// `sqb cost history --json` text of the shown runs. #[pyfunction] #[pyo3(signature = (*, records, matching_count = None))] -fn format_cost_history_json( +pub(crate) fn format_cost_history_json( records: &Bound<'_, PyAny>, matching_count: Option, ) -> PyResult { @@ -94,7 +97,10 @@ fn format_cost_history_json( /// The `sqb cost --json` document of one run, as a dictionary. #[pyfunction] #[pyo3(signature = (*, record))] -fn cost_output_payload(py: Python<'_>, record: &Bound<'_, PyAny>) -> PyResult> { +pub(crate) fn cost_output_payload( + py: Python<'_>, + record: &Bound<'_, PyAny>, +) -> PyResult> { json_to_python(py, output_payload(&cost_run(record)?), false) } @@ -134,7 +140,7 @@ fn format_cost_breakdown(py: Python<'_>, options: Option>) -> /// The aligned human history of the shown runs. #[pyfunction] #[pyo3(signature = (*, records, matching_count, use_color))] -fn format_cost_history( +pub(crate) fn format_cost_history( records: &Bound<'_, PyAny>, matching_count: usize, use_color: bool, From 3df4e87bae4c5ea73afb5fd84cc8e64389c9597c Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Sat, 17 Oct 2026 01:57:47 +0100 Subject: [PATCH 21/28] refactor: call native entry points directly in dbt_command to debug_command bindings Replace keyword calls through sqlbuild._native attributes with direct Rust calls to the pyfunctions. Entry points that tests replace on the module are left for a follow-up test rewrite. --- .../bindings/_helpers/dbt_command/command.rs | 27 +++-- .../_helpers/dbt_command/init_project.rs | 62 +++++----- .../bindings/_helpers/dbt_events/stream.rs | 5 +- .../src/bindings/_helpers/dbt_interop/cli.rs | 9 +- .../_helpers/dbt_interop/execution.rs | 4 +- .../bindings/_helpers/dbt_interop/graph.rs | 2 +- .../bindings/_helpers/dbt_interop/manifest.rs | 10 +- .../_helpers/dbt_interop/orchestration.rs | 2 +- .../_helpers/dbt_interop/pipeline_phases.rs | 114 ++++++++++-------- .../_helpers/dbt_interop/pipeline_prologue.rs | 48 ++++---- .../src/bindings/_helpers/dbt_interop/plan.rs | 37 +++--- .../_helpers/dbt_interop/plan_output.rs | 114 ++++++++++-------- .../bindings/_helpers/dbt_interop/profile.rs | 4 +- .../dbt_interop/profile_connection.rs | 8 +- .../dbt_interop/reference_resolver.rs | 57 ++++----- .../_helpers/dbt_interop/runtime_config.rs | 27 +++-- .../_helpers/dbt_interop/runtime_execution.rs | 61 +++++----- .../_helpers/dbt_interop/sqlbuild_work.rs | 88 ++++++++------ .../_helpers/debug_command/run_debug.rs | 59 ++++----- 19 files changed, 393 insertions(+), 345 deletions(-) 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 a55df3d778..3a97405419 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/command.rs @@ -6,10 +6,13 @@ 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}; +use crate::bindings::_helpers::compile_driver::python_calls::{ + KeywordArguments, call_with, keywords, +}; 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::plan::render_dbt_interop_plan; use crate::bindings::_helpers::dbt_interop::runtime_config::validate_dbt_execution_args; use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; @@ -92,15 +95,19 @@ fn run_plan(project_dir: &Bound<'_, PyAny>, args: &[String], no_color: bool) -> }, )], )?; - let rendered: String = call_with( - ®istry::native(py, NativeAttr::RenderDbtInteropPlan)?, - &[ - ("plan", plan), - ("json_output", flag(py, options.json_output)), - ("use_color", flag(py, progress.use_color)), - ("display_options", display), - ], - )? + let rendered: String = render_dbt_interop_plan( + py, + Some(keywords( + py, + &[ + ("plan", plan), + ("json_output", flag(py, options.json_output)), + ("use_color", flag(py, progress.use_color)), + ("display_options", display), + ], + )?), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))? .extract()?; stream(py, "stdout")?.call_method1("write", (format!("{rendered}\n"),))?; Ok(0) 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 e020a4268f..81cfed492b 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 @@ -12,7 +12,9 @@ use sqlbuild_core::dbt_process::main::run_dbt_process::run_dbt_process; use sqlbuild_core::dbt_process::models::DbtProcessOutput; use sqlbuild_core::text::main::python_strip::python_strip; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; +use crate::bindings::_helpers::dbt_interop::profile::build_dbt_init_project_toml; +use crate::bindings::_helpers::dbt_interop::profile::normalize_dbt_profile_connection; use crate::bindings::_helpers::dbt_interop::profile_load::{ load_dbt_project_metadata, load_selected_dbt_profile_output, }; @@ -24,8 +26,7 @@ use sqlbuild_config::dbt_profile::models::DbtProjectMetadata; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::native_host::third_party::ThirdPartyHost; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::ids::RecordId; use sqlbuild_core::constants::PROJECT_CONFIG_FILENAME as PROJECT_FILE; const DEFAULT_OUTPUT_DIR: &str = "sqlbuild_project"; const PROFILES_FILE: &str = "profiles.yml"; @@ -90,39 +91,40 @@ fn build_dbt_init_project<'py>(request: &Bound<'py, PyAny>) -> PyResult = call_with( - ®istry::native(py, NativeAttr::NormalizeDbtProfileConnection)?, - &[("resolved", resolved_output)], - )?; + let normalized: Bound<'py, PyAny> = normalize_dbt_profile_connection(py, resolved_output)?; let target_path: Bound<'py, PyAny> = resolved( &paths.dbt_project_dir, &path_of(py, &PyString::new(py, &metadata.target_path).into_any())?, )?; let project_name: String = metadata.project_name.replace('-', "_"); let target_name: Bound<'py, PyAny> = native_field(&selected, "target_name")?; - let toml: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::BuildDbtInitProjectToml)?, - &[ - ("project_name", project_name.clone().into_bound_py_any(py)?), - ("adapter", native_field(&normalized, "adapter")?), - ("target_name", target_name.clone()), - ("profile_name", profile_name.clone()), - ( - "dbt_project_dir", - display_path(&paths.dbt_project_dir, &output_dir)?, - ), - ( - "profiles_dir", - display_path(&paths.profiles_dir, &output_dir)?, - ), - ("target_path", display_path(&target_path, &output_dir)?), - ("target_schema", native_field(&normalized, "target_schema")?), - ( - "target_database", - native_field(&normalized, "target_database")?, - ), - ], - )?; + let toml: Bound<'py, PyAny> = build_dbt_init_project_toml( + py, + Some(keywords( + py, + &[ + ("project_name", project_name.clone().into_bound_py_any(py)?), + ("adapter", native_field(&normalized, "adapter")?), + ("target_name", target_name.clone()), + ("profile_name", profile_name.clone()), + ( + "dbt_project_dir", + display_path(&paths.dbt_project_dir, &output_dir)?, + ), + ( + "profiles_dir", + display_path(&paths.profiles_dir, &output_dir)?, + ), + ("target_path", display_path(&target_path, &output_dir)?), + ("target_schema", native_field(&normalized, "target_schema")?), + ( + "target_database", + native_field(&normalized, "target_database")?, + ), + ], + )?), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; progress(&callbacks, "complete", "Rendered dbt profile connection.")?; let mut warnings: Vec> = normalized .getattr("warnings")? diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_events/stream.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_events/stream.rs index 7ccd60218c..75a10144dc 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_events/stream.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_events/stream.rs @@ -121,7 +121,10 @@ impl DbtEventStream { /// One dbt node row and its messages. #[pyfunction] #[pyo3(signature = (**options))] -fn render_dbt_node_result(py: Python<'_>, options: Option>) -> PyResult { +pub(crate) fn render_dbt_node_result( + py: Python<'_>, + options: Option>, +) -> PyResult { let arguments: KeywordArguments<'_> = KeywordArguments::read(py, "render_dbt_node_result", options, RENDER_OPTIONS)?; let position: DbtRowPosition = DbtRowPosition { 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 f4e6cf8cae..b2631d872b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/cli.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/cli.rs @@ -221,7 +221,10 @@ fn ls_node<'py>( /// Nodes from `dbt ls` JSON lines, skipping log lines, non-objects and nodes without an ID. #[pyfunction] #[pyo3(signature = (*, stdout))] -fn parse_dbt_ls_json_lines<'py>(py: Python<'py>, stdout: &str) -> PyResult> { +pub(crate) fn parse_dbt_ls_json_lines<'py>( + py: Python<'py>, + stdout: &str, +) -> PyResult> { ls_nodes(py, stdout) } @@ -293,7 +296,7 @@ fn parsed_flags(parsed: &Bound<'_, PyAny>) -> PyResult { /// Split parsed `sqb dbt ` flags into dbt and SQLBuild buckets. #[pyfunction] #[pyo3(signature = (*, command, parsed))] -fn route_dbt_interop_args<'py>( +pub(crate) fn route_dbt_interop_args<'py>( py: Python<'py>, command: &Bound<'py, PyAny>, parsed: &Bound<'py, PyAny>, @@ -366,7 +369,7 @@ fn config_sources( /// Resolve dbt config from CLI overrides, local config and project config. #[pyfunction] #[pyo3(signature = (**options))] -fn resolve_dbt_config<'py>( +pub(crate) fn resolve_dbt_config<'py>( py: Python<'py>, options: Option>, ) -> PyResult> { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/execution.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/execution.rs index eac5dec3c8..a7e5c92a32 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/execution.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/execution.rs @@ -59,7 +59,7 @@ fn build_merged_dbt_execution_argv<'py>( /// The `dbt ls --output json` that counts an execution argv's selection, when it has a command. #[pyfunction] #[pyo3(signature = (*, argv))] -fn dbt_execution_ls_argv(argv: Vec) -> Option> { +pub(crate) fn dbt_execution_ls_argv(argv: Vec) -> Option> { ls_argv(&argv) } @@ -102,7 +102,7 @@ fn build_failed_sqlbuild_model_names<'py>( /// Render a PASS/WARN/FAIL/SKIP/TOTAL footer for executed dbt nodes. #[pyfunction] #[pyo3(signature = (*, node_results, use_color))] -fn render_dbt_execution_summary_footer( +pub(crate) fn render_dbt_execution_summary_footer( node_results: &Bound<'_, PyAny>, use_color: bool, ) -> PyResult> { 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 6c3666957c..4b569c2c21 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/graph.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/graph.rs @@ -165,7 +165,7 @@ fn project_models<'py>( /// Build the combined graph from a dbt manifest index and a compiled SQLBuild project. #[pyfunction] #[pyo3(signature = (*, manifest, project))] -fn build_dbt_combined_graph<'py>( +pub(crate) fn build_dbt_combined_graph<'py>( py: Python<'py>, manifest: Bound<'py, PyAny>, project: Bound<'py, PyAny>, 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 ec1074ad70..36b7047a13 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/manifest.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/manifest.rs @@ -378,7 +378,7 @@ fn decoded<'py>(contents: &Bound<'py, PyAny>) -> PyResult> { /// Load and index dbt model nodes from a manifest.json file. #[pyfunction] #[pyo3(signature = (*, manifest_path))] -fn load_dbt_manifest_index<'py>( +pub(crate) fn load_dbt_manifest_index<'py>( py: Python<'py>, manifest_path: Bound<'py, PyAny>, ) -> PyResult> { @@ -396,7 +396,7 @@ fn load_dbt_manifest_index<'py>( /// Build a dbt manifest index from discovered manifest contents. #[pyfunction] #[pyo3(signature = (*, manifest_contents))] -fn build_compile_dbt_manifest_index<'py>( +pub(crate) fn build_compile_dbt_manifest_index<'py>( py: Python<'py>, manifest_contents: Bound<'py, PyAny>, ) -> PyResult> { @@ -463,7 +463,7 @@ fn resolve_dbt_manifest_model<'py>( /// Reject ambiguous ownership between dbt and SQLBuild models. #[pyfunction] #[pyo3(signature = (*, known_model_names, dbt_manifest))] -fn validate_compile_dbt_model_names( +pub(crate) fn validate_compile_dbt_model_names( py: Python<'_>, known_model_names: Bound<'_, PyAny>, dbt_manifest: Bound<'_, PyAny>, @@ -499,7 +499,7 @@ fn optional_text(value: &Bound<'_, PyAny>) -> PyResult> { /// Validate one compile-time dbt ref against a dbt manifest. #[pyfunction] #[pyo3(signature = (*, reference, model_relative_path, dbt_manifest))] -fn validate_compile_dbt_model_reference( +pub(crate) fn validate_compile_dbt_model_reference( py: Python<'_>, reference: Bound<'_, PyAny>, model_relative_path: Bound<'_, PyAny>, @@ -564,7 +564,7 @@ pub(crate) fn resolve_sqlbuild_model_dbt_refs<'py>( /// Resolve one external dbt reference to its manifest relation. #[pyfunction] #[pyo3(signature = (**options))] -fn resolve_dbt_reference_relation<'py>( +pub(crate) fn resolve_dbt_reference_relation<'py>( py: Python<'py>, options: Option>, ) -> PyResult> { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/orchestration.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/orchestration.rs index c2584d1439..79de03a7da 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/orchestration.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/orchestration.rs @@ -137,7 +137,7 @@ fn argv_tuples<'py>(py: Python<'py>, argvs: Vec>) -> PyResult( +pub(crate) fn plan_dbt_interop_command<'py>( py: Python<'py>, options: Option>, ) -> PyResult> { 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 e892ea14b2..e964ede527 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 @@ -8,8 +8,13 @@ use crate::bindings::_helpers::build_command::support::{boolean, emit, none, tex use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, keywords, }; +use crate::bindings::_helpers::contracts::spec_resolution::resolve_target_config; +use crate::bindings::_helpers::dbt_interop::execution::render_dbt_execution_summary_footer; use crate::bindings::_helpers::dbt_interop::pipeline_prologue::progress; +use crate::bindings::_helpers::dbt_interop::plan::render_dbt_interop_plan; +use crate::bindings::_helpers::dbt_interop::plan_output::build_sqlbuild_plan_output; use crate::bindings::_helpers::dbt_interop::plan_output::{connection_config, connection_hooks}; +use crate::bindings::_helpers::dbt_interop::sqlbuild_work::execute_dbt_sqlbuild_work; use crate::bindings::_helpers::records::construct::{new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; @@ -93,22 +98,26 @@ fn write_dbt_execution_plan_text( } else { pyo3::types::PyInt::new(py, super::constants::PLAN_DISPLAY_LIMIT).into_any() }; - let rendered = call_with( - ®istry::native(py, NativeAttr::RenderDbtInteropPlan)?, - &[ - ("plan", display), - ("json_output", boolean(py, false)), - ("use_color", native_field(&request, "use_color")?), - ( - "display_options", - new_record_with( - py, - RecordId::DisplayOptions, - &[("max_entries_per_section", limit)], - )?, - ), - ], - )?; + let rendered = render_dbt_interop_plan( + py, + Some(keywords( + py, + &[ + ("plan", display), + ("json_output", boolean(py, false)), + ("use_color", native_field(&request, "use_color")?), + ( + "display_options", + new_record_with( + py, + RecordId::DisplayOptions, + &[("max_entries_per_section", limit)], + )?, + ), + ], + )?), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; emit( &args.value("invocation")?.getattr("output_stream")?, &format!("{}\n\n", rendered.extract::()?), @@ -154,16 +163,13 @@ fn execute_dbt_without_state_tracking<'py>( #[pyo3(signature = (**options))] 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( - ®istry::native(py, NativeAttr::RenderDbtExecutionSummaryFooter)?, - &[ - ( - "node_results", - args.value("execution")?.getattr("node_results")?, - ), - ("use_color", args.value("request")?.getattr("use_color")?), - ], - )?; + let footer = render_dbt_execution_summary_footer( + &args.value("execution")?.getattr("node_results")?, + args.value("request")? + .getattr("use_color")? + .extract::()?, + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; if !footer.is_none() { emit( &args.value("invocation")?.getattr("output_stream")?, @@ -224,13 +230,16 @@ fn resolve_sqlbuild_execution_plan_output<'py>( ), ], )?; - call_with( - ®istry::native(py, NativeAttr::BuildSqlbuildPlanOutput)?, - &[ - ("environment", environment(&args)?), - ("request", request), - ("hooks", connection_hooks(&reporter, none(py))?), - ], + build_sqlbuild_plan_output( + py, + Some(keywords( + py, + &[ + ("environment", environment(&args)?), + ("request", request), + ("hooks", connection_hooks(&reporter, none(py))?), + ], + )?), ) } @@ -242,16 +251,11 @@ fn execution_limits<'py>( if target.is_none() { return new_record(py, RecordId::ExecutionLimitsConfig, (), None); } - call_with( - ®istry::native(py, NativeAttr::ResolveTargetConfig)?, - &[ - ( - "project_config", - native_field(discovered, "project_config")?, - ), - ("local_config", native_field(discovered, "local_config")?), - ("target_name", target), - ], + resolve_target_config( + py, + &native_field(discovered, "project_config")?, + &native_field(discovered, "local_config")?, + &target, )? .getattr("execution_limits") } @@ -292,17 +296,21 @@ fn run_dbt_sqlbuild_work<'py>( ), ], )?; - call_with( - ®istry::native(py, NativeAttr::ExecuteDbtSqlbuildWork)?, - &[ - ("context", context), - ("command", native_field(&request, "command")?), - ("project", project), - ("project_dir", native_field(&request, "project_dir")?), - ("fail_fast", native_field(&request, "fail_fast")?), - ("verbose", native_field(&request, "verbose")?), - ], + execute_dbt_sqlbuild_work( + py, + Some(keywords( + py, + &[ + ("context", context), + ("command", native_field(&request, "command")?), + ("project", project), + ("project_dir", native_field(&request, "project_dir")?), + ("fail_fast", native_field(&request, "fail_fast")?), + ("verbose", native_field(&request, "verbose")?), + ], + )?), ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py)) } /// Report a SQLBuild skip transition without writing machine output to stdout. 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 4d27733dc7..4bb5b3f5ef 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 @@ -4,6 +4,10 @@ use crate::bindings::_helpers::compile_driver::discovery::discover_project_input use crate::bindings::_helpers::compile_driver::project::build_compiled_project; use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::dbt_interop::cli::route_dbt_interop_args; +use crate::bindings::_helpers::dbt_interop::graph::build_dbt_combined_graph; +use crate::bindings::_helpers::dbt_interop::manifest::load_dbt_manifest_index; +use crate::bindings::_helpers::dbt_interop::orchestration::plan_dbt_interop_command; use crate::bindings::_helpers::records::construct::new_record_with; use pyo3::prelude::{ Bound, PyAny, PyAnyMethods, PyErr, PyModule, PyModuleMethods, PyResult, Python, @@ -77,10 +81,7 @@ pub(crate) fn resolve_dbt_execution_invocation<'py>( } let command = native_field(request, "command")?; let parsed = parse_dbt_execution_args(&command, &native_field(request, "args")?)?; - let routed = call_with( - ®istry::native(py, NativeAttr::RouteDbtInteropArgs)?, - &[("command", command), ("parsed", parsed)], - )?; + let routed = route_dbt_interop_args(py, &command, &parsed)?; let project_dir = native_field(request, "project_dir")?; let discovered = discover_project_inputs( py, @@ -163,10 +164,7 @@ fn load_compiled_dbt_manifest<'py>( let start = Instant::now(); progress(&callback, "Loading dbt manifest...")?; let path = manifest_path(&args.value("dbt_options")?)?; - let manifest = call_with( - ®istry::native(py, NativeAttr::LoadDbtManifestIndex)?, - &[("manifest_path", path)], - )?; + let manifest = load_dbt_manifest_index(py, path)?; completed(&callback, "Loaded dbt manifest.", start)?; Ok(manifest) } @@ -236,13 +234,8 @@ fn resolve_dbt_interop_plan<'py>( let start = Instant::now(); progress(&callback, "Building dbt interop graph...")?; let project = args.value("compiled")?.getattr("project")?; - let graph = call_with( - ®istry::native(py, NativeAttr::BuildDbtCombinedGraph)?, - &[ - ("manifest", args.value("manifest")?), - ("project", project.clone()), - ], - )?; + let graph = build_dbt_combined_graph(py, args.value("manifest")?, project.clone()) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; completed(&callback, "Built dbt interop graph.", start)?; let start = Instant::now(); progress(&callback, "Resolving dbt and SQLBuild selection...")?; @@ -273,17 +266,20 @@ fn resolve_dbt_interop_plan<'py>( ("sqlbuild_executable", args.value("sqlbuild_executable")?), ], )?; - let plan = call_with( - ®istry::native(py, NativeAttr::PlanDbtInteropCommand)?, - &[ - ("command", args.value("command")?), - ("project", project), - ("manifest", args.value("manifest")?), - ("graph", graph.clone()), - ("dbt_runner", native_field(&invocation, "runner")?), - ("dbt_options", native_field(&invocation, "dbt_options")?), - ("command_args", command_args), - ], + let plan = plan_dbt_interop_command( + py, + Some(keywords( + py, + &[ + ("command", args.value("command")?), + ("project", project), + ("manifest", args.value("manifest")?), + ("graph", graph.clone()), + ("dbt_runner", native_field(&invocation, "runner")?), + ("dbt_options", native_field(&invocation, "dbt_options")?), + ("command_args", command_args), + ], + )?), )?; completed(&callback, "Resolved dbt and SQLBuild selection.", start)?; new_record_with( 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 86a8b1f23b..31e7cce43b 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,13 @@ 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}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, keywords}; +use crate::bindings::_helpers::plan_output::plan_text::format_plan; use crate::bindings::_helpers::records::construct::{ enum_from_value, enum_member, new_record, new_record_with, }; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::ids::RecordId; const BUILD_OPTIONS: (&[&str], &[&str]) = ( &[ @@ -206,7 +206,10 @@ fn display_limit(options: &Bound<'_, PyAny>) -> PyResult { /// Render a dbt interop plan in the requested CLI output format. #[pyfunction] #[pyo3(signature = (**options))] -fn render_dbt_interop_plan(py: Python<'_>, options: Option>) -> PyResult { +pub(crate) fn render_dbt_interop_plan( + py: Python<'_>, + options: Option>, +) -> PyResult { let arguments: KeywordArguments<'_> = KeywordArguments::read(py, "render_dbt_interop_plan", options, RENDER_OPTIONS)?; let plan: Bound<'_, PyAny> = arguments.value("plan")?; @@ -225,17 +228,21 @@ fn render_dbt_interop_plan(py: Python<'_>, options: Option>) - None } else { Some( - call_with( - ®istry::native(py, NativeAttr::FormatPlan)?, - &[ - ("plan", plan_output), - ( - "use_color", - PyBool::new(py, use_color).to_owned().into_any(), - ), - ("display_options", display.clone()), - ], - )? + format_plan( + py, + Some(keywords( + py, + &[ + ("plan", plan_output), + ( + "use_color", + PyBool::new(py, use_color).to_owned().into_any(), + ), + ("display_options", display.clone()), + ], + )?), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))? .extract()?, ) }; 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 a750afd466..59756ca58f 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 @@ -8,15 +8,20 @@ use crate::bindings::_helpers::build_command::support::{boolean, keyword_relay, use crate::bindings::_helpers::command_runtime::connection::{ anchor_duckdb_database, with_dbt_profile, }; +use crate::bindings::_helpers::command_runtime::warehouses::apply_command_warehouse; +use crate::bindings::_helpers::compile_attachment::effective_config::build_effective_connection_config; +use crate::bindings::_helpers::compile_driver::deferral::resolve_model_deferral_inputs; use crate::bindings::_helpers::compile_driver::plan_result::connection_with_hooks; 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::planner_entries::display_plan::build_display_only_sqlbuild_plan; +use crate::bindings::_helpers::planner_entries::execution_plan::build_execution_plan; use crate::bindings::_helpers::records::construct::{new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; const BUILD_OPTIONS: (&[&str], &[&str]) = (&["environment", "request", "hooks"], &[]); @@ -38,10 +43,7 @@ pub(crate) fn connection_config<'py>( ) -> PyResult> { let py = environment.py(); let discovered = native_field(environment, "discovered_inputs")?; - let raw = call_with( - ®istry::native(py, NativeAttr::BuildEffectiveConnectionConfig)?, - &[("discovered_inputs", discovered.clone())], - )?; + let raw = build_effective_connection_config(&discovered.clone(), None, None)?; let raw: Bound<'_, PyDict> = raw.cast_into()?; let project_dir = native_field(environment, "project_dir")?; let adapter_name = native_field(environment, "adapter_name")?; @@ -56,15 +58,18 @@ pub(crate) fn connection_config<'py>( (&project_dir, &adapter_name.str()?.extract::()?), )?; let config = config.into_any(); - call_with( - ®istry::native(py, NativeAttr::ApplyCommandWarehouse)?, - &[ - ("config", config), - ("adapter_name", native_field(environment, "adapter_name")?), - ("discovered_inputs", discovered), - ("project_dir", native_field(environment, "project_dir")?), - ("selected_target", none(py)), - ], + apply_command_warehouse( + py, + Some(keywords( + py, + &[ + ("config", config), + ("adapter_name", native_field(environment, "adapter_name")?), + ("discovered_inputs", discovered), + ("project_dir", native_field(environment, "project_dir")?), + ("selected_target", none(py)), + ], + )?), ) } @@ -149,18 +154,21 @@ pub(crate) fn build_sqlbuild_plan_output<'py>( let connection = open_connection(&environment, connection_config(&environment)?, &hooks)?; let planned = (|| { let args = native_field(&request, "sqlbuild_args")?; - let deferral = call_with( - ®istry::native(py, NativeAttr::ResolveModelDeferralInputs)?, - &[ - ("project", native_field(&environment, "project")?), - ( - "discovered_inputs", - native_field(&environment, "discovered_inputs")?, - ), - ("adapter", native_field(&environment, "adapter")?), - ("connection", connection.clone()), - ("defer_to", flag_value(&args, "--defer-to")?), - ], + let deferral = resolve_model_deferral_inputs( + py, + Some(keywords( + py, + &[ + ("project", native_field(&environment, "project")?), + ( + "discovered_inputs", + native_field(&environment, "discovered_inputs")?, + ), + ("adapter", native_field(&environment, "adapter")?), + ("connection", connection.clone()), + ("defer_to", flag_value(&args, "--defer-to")?), + ], + )?), )?; let cursor = new_record_with( py, @@ -191,36 +199,36 @@ pub(crate) fn build_sqlbuild_plan_output<'py>( ], )?; let discovered = native_field(&environment, "discovered_inputs")?; - let result = call_with( - ®istry::native(py, NativeAttr::BuildExecutionPlan)?, - &[ - ("project", native_field(&environment, "project")?), - ("adapter", native_field(&environment, "adapter")?), - ("connection", connection.clone()), - ("selection", selection), - ("overrides", overrides), - ("deferral", deferral), - ( - "policies", - new_record(py, RecordId::PlannerPolicies, (), None)?, - ), - ("on_progress", native_field(&hooks, "on_progress")?), - ( - "project_config", - native_field(&discovered, "project_config")?, - ), - ("local_config", native_field(&discovered, "local_config")?), - ], + let result = build_execution_plan( + py, + Some(keywords( + py, + &[ + ("project", native_field(&environment, "project")?), + ("adapter", native_field(&environment, "adapter")?), + ("connection", connection.clone()), + ("selection", selection), + ("overrides", overrides), + ("deferral", deferral), + ( + "policies", + new_record(py, RecordId::PlannerPolicies, (), None)?, + ), + ("on_progress", native_field(&hooks, "on_progress")?), + ( + "project_config", + native_field(&discovered, "project_config")?, + ), + ("local_config", native_field(&discovered, "local_config")?), + ], + )?), ); match result { Err(error) if error.is_instance(py, ®istry::error(py, ErrorId::PlannerInput)?) => { - call_with( - ®istry::native(py, NativeAttr::BuildDisplayOnlySqlbuildPlan)?, - &[ - ("project", native_field(&environment, "project")?), - ("selected_model_names", selected), - ("full_refresh", full_refresh), - ], + build_display_only_sqlbuild_plan( + &native_field(&environment, "project")?, + &selected, + full_refresh.extract::()?, ) } result => result, 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 1db6527af0..fd9599abdf 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/profile.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/profile.rs @@ -103,7 +103,7 @@ fn normalized_connection<'py>( /// Normalize rendered dbt profile output for SQLBuild. #[pyfunction] #[pyo3(signature = (*, resolved))] -fn normalize_dbt_profile_connection<'py>( +pub(crate) fn normalize_dbt_profile_connection<'py>( py: Python<'py>, resolved: Bound<'py, PyAny>, ) -> PyResult> { @@ -132,7 +132,7 @@ fn text_argument(arguments: &KeywordArguments<'_>, name: &str) -> PyResult, options: Option>, ) -> PyResult { 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 8ba22b6097..fa51865871 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 @@ -9,14 +9,13 @@ use sqlbuild_core::text::main::python_strip::python_strip; 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::normalize_dbt_profile_connection; 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::fields::field as native_field; -use crate::bindings::_helpers::records::ids::NativeAttr; -use crate::bindings::_helpers::records::registry; const DBT_PROFILE_SOURCE: &str = "dbt_profile"; const PROFILE_CONNECTION_OPTIONS: (&[&str], &[&str]) = ( @@ -98,10 +97,7 @@ fn profile_connection<'py>( ("cli_vars", cli_vars), ], )?; - call_with( - ®istry::native(py, NativeAttr::NormalizeDbtProfileConnection)?, - &[("resolved", resolved)], - ) + normalize_dbt_profile_connection(py, resolved) } /// Resolve a raw connection config if it references a dbt profile. 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 5b9db65f90..398092fc20 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,15 @@ use std::collections::HashSet; use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, keywords, within, }; +use crate::bindings::_helpers::dbt_interop::manifest::build_compile_dbt_manifest_index; +use crate::bindings::_helpers::dbt_interop::manifest::resolve_dbt_reference_relation; +use crate::bindings::_helpers::dbt_interop::manifest::validate_compile_dbt_model_names; +use crate::bindings::_helpers::dbt_interop::manifest::validate_compile_dbt_model_reference; use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; const VALIDATE_OPTIONS: (&[&str], &[&str]) = ( @@ -107,13 +111,8 @@ impl DbtCompileReferenceResolver { py: Python<'_>, known_model_names: Bound<'_, PyAny>, ) -> Result<(), PyErr> { - call_with( - ®istry::native(py, NativeAttr::ValidateCompileDbtModelNames)?, - &[ - ("known_model_names", known_model_names), - ("dbt_manifest", self.manifest_or_none(py)), - ], - )?; + validate_compile_dbt_model_names(py, known_model_names, self.manifest_or_none(py)) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; Ok(()) } @@ -267,17 +266,13 @@ impl DbtCompileReferenceResolver { ("ref_package", arguments.value("ref_package")?), ], )?; - call_with( - ®istry::native(py, NativeAttr::ValidateCompileDbtModelReference)?, - &[ - ("reference", reference), - ( - "model_relative_path", - arguments.value("owner_relative_sql_path")?, - ), - ("dbt_manifest", self.manifest_or_none(py)), - ], - )?; + validate_compile_dbt_model_reference( + py, + reference, + arguments.value("owner_relative_sql_path")?, + self.manifest_or_none(py), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; Ok(()) } @@ -289,14 +284,17 @@ impl DbtCompileReferenceResolver { ref_name: Bound<'py, PyAny>, ref_package: Bound<'py, PyAny>, ) -> PyResult> { - call_with( - ®istry::native(py, NativeAttr::ResolveDbtReferenceRelation)?, - &[ - ("manifest", self.manifest_or_none(py)), - ("ref_kind", ref_kind), - ("ref_name", ref_name), - ("ref_package", ref_package), - ], + resolve_dbt_reference_relation( + py, + Some(keywords( + py, + &[ + ("manifest", self.manifest_or_none(py)), + ("ref_kind", ref_kind), + ("ref_name", ref_name), + ("ref_package", ref_package), + ], + )?), ) } } @@ -311,10 +309,7 @@ pub(crate) fn build_compile_reference_resolver<'py>( if manifest_contents.is_none() { return Ok(py.None().into_bound(py)); } - let manifest: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::BuildCompileDbtManifestIndex)?, - &[("manifest_contents", manifest_contents)], - )?; + let manifest: Bound<'py, PyAny> = build_compile_dbt_manifest_index(py, manifest_contents)?; Ok(Bound::new( py, DbtCompileReferenceResolver::new(Some(manifest.unbind())), 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 fe2dc9dda0..d48a5d991d 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,14 +12,14 @@ 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; +use crate::bindings::_helpers::compile_driver::python_calls::{call_with, keywords}; +use crate::bindings::_helpers::dbt_interop::cli::resolve_dbt_config; 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::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; const ARGUMENT_ERROR: ErrorId = ErrorId::DbtInteropArgument; const CONFIG_ERROR: ErrorId = ErrorId::DbtInteropConfig; @@ -268,15 +268,18 @@ pub(crate) fn plan_options<'py>( .getattr("dbt")?; let local_config: Bound<'py, PyAny> = native_field(discovered_inputs, "local_config")?.getattr("dbt")?; - let resolved: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::ResolveDbtConfig)?, - &[ - ("project_root", project_dir.clone()), - ("config", project_config.clone()), - ("local_config", local_config.clone()), - ("overrides", config_overrides(py, args)?), - ("require_project_dir", flag(py, true)), - ], + let resolved: Bound<'py, PyAny> = resolve_dbt_config( + py, + Some(keywords( + py, + &[ + ("project_root", project_dir.clone()), + ("config", project_config.clone()), + ("local_config", local_config.clone()), + ("overrides", config_overrides(py, args)?), + ("require_project_dir", flag(py, true)), + ], + )?), )?; let values: Bound<'py, PyDict> = vars_mapping(&project_config, &local_config, args)?; let vars: Bound<'py, PyAny> = if values.is_empty() { 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 61e37ed30b..4ec255e364 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,6 +1,11 @@ //! Execute dbt through the subprocess boundary and finalize results in artifact order. -use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::compile_driver::python_calls::{ + KeywordArguments, call_with, keywords, +}; +use crate::bindings::_helpers::dbt_events::stream::render_dbt_node_result; +use crate::bindings::_helpers::dbt_interop::cli::parse_dbt_ls_json_lines; +use crate::bindings::_helpers::dbt_interop::execution::dbt_execution_ls_argv; use crate::bindings::_helpers::dbt_interop::runtime_results::{append_missing, result_ids}; use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; @@ -40,9 +45,9 @@ fn expected_total<'py>( argv: &Bound<'py, PyAny>, ) -> PyResult> { let py = argv.py(); - let ls = call_with( - ®istry::native(py, NativeAttr::DbtExecutionLsArgv)?, - &[("argv", argv.clone())], + let ls = pyo3::IntoPyObjectExt::into_bound_py_any( + dbt_execution_ls_argv(argv.clone().extract::>()?), + py, )?; if ls.is_none() { return Ok(py.None().into_bound(py)); @@ -84,10 +89,8 @@ fn expected_total<'py>( if native_field(&result, "returncode")?.extract::()? != 0 { return Ok(py.None().into_bound(py)); } - let nodes = call_with( - ®istry::native(py, NativeAttr::ParseDbtLsJsonLines)?, - &[("stdout", native_field(&result, "stdout")?)], - )?; + let nodes = parse_dbt_ls_json_lines(py, &native_field(&result, "stdout")?.extract::()?) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; Ok(PyInt::new(py, nodes.len()?).into_any()) } @@ -212,25 +215,29 @@ fn execute_dbt_commands<'py>( } let count = results.len()? - appended.len(); for (offset, result) in appended.into_iter().enumerate() { - let rendered = call_with( - ®istry::native(py, NativeAttr::RenderDbtNodeResult)?, - &[ - ("result", result.clone()), - ("use_color", native_field(&style, "use_color")?), - ( - "display_index", - PyInt::new(py, count + offset + 1).into_any(), - ), - ( - "display_total", - if total.is_none() { - PyInt::new(py, results.len()?).into_any() - } else { - total.clone() - }, - ), - ], - )?; + let rendered = render_dbt_node_result( + py, + Some(keywords( + py, + &[ + ("result", result.clone()), + ("use_color", native_field(&style, "use_color")?), + ( + "display_index", + PyInt::new(py, count + offset + 1).into_any(), + ), + ( + "display_total", + if total.is_none() { + PyInt::new(py, results.len()?).into_any() + } else { + total.clone() + }, + ), + ], + )?), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; write( &arguments.value("stdout_stream")?, &rendered.extract::()?, 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 cf5cd7eb26..2e033efe8b 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 @@ -8,10 +8,15 @@ use sqlbuild_core::build_command::constants::{ SNAPSHOT_FULL_REFRESH_DBT_HELP, TABLE_TYPE_DOWNGRADE_DBT_HELP, }; +use crate::bindings::_helpers::build_command::policies::enforce_plan_safety_policies_binding; 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}; +use crate::bindings::_helpers::build_progress::footer::format_build_footer; +use crate::bindings::_helpers::cli::progress_text::format_execution_header; +use crate::bindings::_helpers::compile_driver::python_calls::{ + KeywordArguments, call_with, keywords, +}; use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::records::construct::{new_record_with, record_class}; @@ -69,7 +74,7 @@ fn dbt_interop_plan_safety_gates(py: Python<'_>) -> PyResult> { /// Execute SQLBuild work only after its plan safety policies succeed. #[pyfunction] #[pyo3(signature = (**options))] -fn execute_dbt_sqlbuild_work<'py>( +pub(crate) fn execute_dbt_sqlbuild_work<'py>( py: Python<'py>, options: Option>, ) -> PyResult { @@ -79,29 +84,33 @@ fn execute_dbt_sqlbuild_work<'py>( let plan = native_field(&context, "plan_output")?; let output = native_field(&context, "output_stream")?; let color = native_field(&context, "use_color")?; - call_with( - ®istry::native(py, NativeAttr::EnforcePlanSafetyPolicies)?, - &[ - ("plan", plan.clone()), - ("gates", dbt_interop_plan_safety_gates(py)?), - ( - "snapshots_config", - native_field(&context, "snapshots_config")?, - ), - ( - "target_name", - native_field(&project, "effective_target_name")?, - ), - ( - "execution_limits", - native_field(&context, "execution_limits")?, - ), - ( - "streams", - tuple(py, &[py.import("sys")?.getattr("stdin")?, output.clone()])?, - ), - ], - )?; + enforce_plan_safety_policies_binding( + py, + Some(keywords( + py, + &[ + ("plan", plan.clone()), + ("gates", dbt_interop_plan_safety_gates(py)?), + ( + "snapshots_config", + native_field(&context, "snapshots_config")?, + ), + ( + "target_name", + native_field(&project, "effective_target_name")?, + ), + ( + "execution_limits", + native_field(&context, "execution_limits")?, + ), + ( + "streams", + tuple(py, &[py.import("sys")?.getattr("stdin")?, output.clone()])?, + ), + ], + )?), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; let callbacks = call_with( ®istry::native(py, NativeAttr::BuildProgressCallbacks)?, &[ @@ -122,13 +131,13 @@ fn execute_dbt_sqlbuild_work<'py>( ) }; let concurrency = native_field(&project, "settings")?.getattr("concurrency")?; - let header = call_with( - ®istry::native(py, NativeAttr::FormatExecutionHeader)?, - &[ - ("command", text(py, &display)), - ("target", none(py)), - ("concurrency", concurrency.clone()), - ], + let header = pyo3::IntoPyObjectExt::into_bound_py_any( + format_execution_header( + &text(py, &display).extract::()?, + none(py).extract::>()?.as_deref(), + concurrency.clone().extract::()?, + ), + py, )?; let style = call_with( ®istry::native(py, NativeAttr::CliStyle)?, @@ -230,14 +239,13 @@ fn execute_dbt_sqlbuild_work<'py>( ("result", result.clone()), ], )?; - let footer = call_with( - ®istry::native(py, NativeAttr::FormatBuildFooter)?, - &[ - ("result", result.clone()), - ("elapsed", native_field(&callbacks, "elapsed")?), - ("use_color", color), - ], - )?; + let footer = format_build_footer( + &result.clone(), + native_field(&callbacks, "elapsed")?.extract::()?, + color.extract::()?, + None, + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; emit(&output, &format!("\n{}\n", footer.extract::()?))?; Ok(i64::from(!native_field(&result, "status")?.eq( 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 c38203bc8b..e1bb0b6f23 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 @@ -30,6 +30,7 @@ use crate::bindings::_helpers::command_runtime::warehouse_scope::command_warehou use crate::bindings::_helpers::command_runtime::warehouses::{ CommandScope, WarehouseInputs, apply_warehouse, command_scope, find_authored, resolve_warehouse, }; +use crate::bindings::_helpers::compile_attachment::target::build_effective_target_namespace; use crate::bindings::_helpers::compile_driver::discovery::discover_project_inputs; use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::compile_driver::python_calls::{ @@ -303,33 +304,37 @@ fn build_report( })?; let connection_config: Bound<'_, PyDict> = effective_connection(&context, (&adapter_name, &base_connection))?.cast_into()?; - let namespace: Bound<'_, PyTuple> = call_with( - ®istry::native(py, NativeAttr::BuildEffectiveTargetNamespace)?, - &[ - ("discovered_inputs", context.discovered_inputs.clone()), - ("selected_target", context.selected_target.clone()), - ( - "connection_database", - connection_config - .get_item("database")? - .unwrap_or_else(|| py.None().into_bound(py)), - ), - ( - "connection_schema", - connection_config - .get_item("schema")? - .unwrap_or_else(|| py.None().into_bound(py)), - ), - ( - "default_database", - context.adapter.call_method0("default_database")?, - ), - ( - "default_schema", - context.adapter.call_method0("default_schema")?, - ), - ], - )? + let namespace: Bound<'_, PyTuple> = build_effective_target_namespace( + py, + Some(keywords( + py, + &[ + ("discovered_inputs", context.discovered_inputs.clone()), + ("selected_target", context.selected_target.clone()), + ( + "connection_database", + connection_config + .get_item("database")? + .unwrap_or_else(|| py.None().into_bound(py)), + ), + ( + "connection_schema", + connection_config + .get_item("schema")? + .unwrap_or_else(|| py.None().into_bound(py)), + ), + ( + "default_database", + context.adapter.call_method0("default_database")?, + ), + ( + "default_schema", + context.adapter.call_method0("default_schema")?, + ), + ], + )?), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))? .cast_into()?; let target_config: Bound<'_, PyAny> = namespace.get_item(1)?; let connection_name: Option = if target_config.is_none() { From 7bf3bbb75baf116032fa4db2f02535d970dd8d71 Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Sat, 17 Oct 2026 01:57:47 +0100 Subject: [PATCH 22/28] refactor: call native entry points directly in format_command to load bindings Replace keyword calls through sqlbuild._native attributes with direct Rust calls to the pyfunctions. Entry points that tests replace on the module are left for a follow-up test rewrite. --- .../_helpers/format_command/config.rs | 16 +++-- .../bindings/_helpers/format_command/run.rs | 68 ++++++++++--------- .../_helpers/format_command/selection.rs | 27 ++++---- .../bindings/_helpers/load/load_command.rs | 65 ++++++++---------- .../bindings/_helpers/load/load_selection.rs | 25 +++---- 5 files changed, 94 insertions(+), 107 deletions(-) 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 4627f308ac..64a98f3881 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; use crate::bindings::_helpers::records::construct::new_record_with; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::rules::rules_config::load_rules_config; /// Format settings and whether lint suppressions are allowed. pub(crate) struct FormatConfig { @@ -29,10 +28,13 @@ pub(crate) fn format_config( let settings: FormatSettings = resolve_format_settings(Path::new(&directory)).map_err(PyValueError::new_err)?; let allow_suppressions: bool = if settings.has_project_config { - call_with( - ®istry::native(py, NativeAttr::LoadRulesConfig)?, - &[("project_dir", project_dir.call_method0("resolve")?)], - )? + load_rules_config( + py, + project_dir + .call_method0("resolve")? + .extract::()?, + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))? .getattr("allow_exceptions")? .extract()? } else { 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 9677ca951b..5cc17ddfc7 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/format_command/run.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/format_command/run.rs @@ -8,7 +8,7 @@ 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::boundary::panics::value_error; -use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::compile_driver::python_calls::{call_with, keywords}; 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, @@ -17,6 +17,7 @@ use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr}; use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::dsl_headers::{header_violations, rules_config}; +use crate::bindings::_helpers::rules::fixture_formatting::format_redundant_fixture_nulls; use crate::bindings::_helpers::rules::lint_pipeline::{LintViolationRow, ViolationClasses}; use crate::bindings::_helpers::rules::lint_text::apply_lint_suppressions; use sqlbuild_rules::dsl_headers::main::scan_headers::scan_headers; @@ -61,15 +62,13 @@ fn fixture_autofix<'py>( let Some((discovered, syntax)) = fixture_inputs else { return Ok(PyDict::new(py)); }; - Ok(call_with( - ®istry::native(py, NativeAttr::FormatRedundantFixtureNulls)?, - &[ - ("files", files.clone().into_any()), - ("project_dir", request.project_dir.clone()), - ("discovered_inputs", discovered.clone()), - ("sql_lexical_syntax", syntax.clone()), - ], - )? + Ok(format_redundant_fixture_nulls( + &files.clone().into_any().cast_into::()?, + &request.project_dir.clone(), + &discovered.clone(), + &syntax.clone(), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))? .downcast_into::()?) } @@ -215,29 +214,32 @@ fn plan_rule_fixes<'py>( files: &Bound<'py, PyDict>, ) -> PyResult<(Bound<'py, PyDict>, Bound<'py, PyAny>, Bound<'py, PyAny>)> { let py: Python<'py> = files.py(); - let result: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::PlanRuleFixes)?, - &[ - ("project_dir", request.project_dir.clone()), - ("files", files.clone().into_any()), - ("config", request.config.clone()), - ( - "adapter", - request - .adapter - .map_or_else(|| py.None().into_bound(py), Clone::clone), - ), - ( - "discovered_inputs", - request - .discovered - .map_or_else(|| py.None().into_bound(py), Clone::clone), - ), - ( - "enabled", - PyBool::new(py, request.fix).to_owned().into_any(), - ), - ], + let result: Bound<'py, PyAny> = crate::bindings::_helpers::rules::fix_session::plan_rule_fixes( + py, + Some(keywords( + py, + &[ + ("project_dir", request.project_dir.clone()), + ("files", files.clone().into_any()), + ("config", request.config.clone()), + ( + "adapter", + request + .adapter + .map_or_else(|| py.None().into_bound(py), Clone::clone), + ), + ( + "discovered_inputs", + request + .discovered + .map_or_else(|| py.None().into_bound(py), Clone::clone), + ), + ( + "enabled", + PyBool::new(py, request.fix).to_owned().into_any(), + ), + ], + )?), )?; Ok(( result.get_item(0)?.downcast_into::()?, 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 a207114618..4053a4e94b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/format_command/selection.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/format_command/selection.rs @@ -18,6 +18,8 @@ use crate::bindings::_helpers::command_runtime::adapters::resolve_cli_adapter; use crate::bindings::_helpers::compile_driver::discovery::discover_project_inputs; use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, within}; +use crate::bindings::_helpers::planner_selection::selectors::select_project_graph; +use crate::bindings::_helpers::planner_selection::unit_tests::reject_unit_test_selectors; use crate::bindings::_helpers::records::construct::{enum_member, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; @@ -164,14 +166,12 @@ fn model_paths( ("adapter", scope.adapter.clone()), ], )?; - let keys: Bound<'_, PyAny> = call_with( - ®istry::native(py, NativeAttr::SelectProjectGraph)?, - &[ - ("graph", graph.clone()), - ("select", selectors(py, select)?), - ("exclude", selectors(py, exclude)?), - ], - )?; + let keys: Bound<'_, PyAny> = select_project_graph( + &graph.clone(), + &selectors(py, select)?, + &selectors(py, exclude)?, + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; let model_type: Bound<'_, PyAny> = enum_member(py, RecordId::CompiledResourceType, "MODEL")?; let mut names: BTreeSet = BTreeSet::new(); for key in keys.try_iter()? { @@ -347,13 +347,10 @@ pub(crate) fn resolve_lint_inputs<'py>( ], )?), )?; - call_with( - ®istry::native(py, NativeAttr::RejectUnitTestSelectors)?, - &[ - ("select", selectors(py, select)?), - ("exclude", selectors(py, exclude)?), - ("discovered_inputs", discovered.clone()), - ], + reject_unit_test_selectors( + &selectors(py, select)?, + &selectors(py, exclude)?, + &discovered.clone(), )?; let adapter: Bound<'py, PyAny> = discovered_adapter(project_dir, &discovered)?; if select.is_empty() && exclude.is_empty() { 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 b5ac4dab09..19e7541dc0 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/load/load_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/load/load_command.rs @@ -17,6 +17,8 @@ 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_attachment::effective_config::build_effective_runtime_config; +use crate::bindings::_helpers::compile_attachment::effective_config::build_effective_settings_config; use crate::bindings::_helpers::compile_driver::discovery::discover_project_inputs; use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::compile_driver::python_calls::keywords; @@ -31,11 +33,14 @@ 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::planner_selection::unit_tests::reject_unit_test_selectors; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; use crate::bindings::_helpers::records::construct::new_record; use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::result_output::documents::format_load_execution_json; +use crate::bindings::_helpers::result_output::documents::write_execution_json_output; const LOADER_NODE_KEY: &str = "sqlbuild_loader_node"; const EXECUTION_COMMAND: &str = "sqb load"; @@ -84,14 +89,7 @@ fn invocation<'py>(py: Python<'py>, request: &Bound<'py, PyAny>) -> PyResult = native_field(request, "select")?; let exclude: Bound<'py, PyAny> = native_field(request, "exclude")?; - call_with( - ®istry::native(py, NativeAttr::RejectUnitTestSelectors)?, - &[ - ("select", select.clone()), - ("exclude", exclude.clone()), - ("discovered_inputs", discovered.clone()), - ], - )?; + reject_unit_test_selectors(&select.clone(), &exclude.clone(), &discovered.clone())?; let selected_target: Bound<'py, PyAny> = native_field(request, "selected_target")?; let target_config: Bound<'py, PyAny> = call_with( ®istry::native(py, NativeAttr::BuildEffectiveTargetConfig)?, @@ -292,23 +290,19 @@ fn prepare<'py>( ], )?; check_reference_targets(invocation, (&adapter, &connection_config))?; - let runtime: (Bound<'py, PyAny>, Bound<'py, PyAny>, Bound<'py, PyAny>) = call_with( - ®istry::native(py, NativeAttr::BuildEffectiveRuntimeConfig)?, - &[ - ("discovered_inputs", discovered.clone()), - ("selected_target", native_field(request, "selected_target")?), - ("cli_vars", native_field(request, "cli_vars")?), - ], - )? - .extract()?; + let runtime: (Bound<'py, PyAny>, Bound<'py, PyAny>, Bound<'py, PyAny>) = + build_effective_runtime_config( + &discovered.clone(), + Some(native_field(request, "selected_target")?).filter(|value| !value.is_none()), + Some(native_field(request, "cli_vars")?).filter(|value| !value.is_none()), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))? + .extract()?; let requested: Bound<'py, PyAny> = native_field(request, "concurrency")?; let concurrency: i64 = if requested.is_none() { - call_with( - ®istry::native(py, NativeAttr::BuildEffectiveSettingsConfig)?, - &[("discovered_inputs", discovered.clone())], - )? - .getattr("concurrency")? - .extract()? + build_effective_settings_config(&discovered.clone())? + .getattr("concurrency")? + .extract()? } else { requested.extract()? }; @@ -509,21 +503,16 @@ fn execute<'py>( fn write_json(request: &Bound<'_, PyAny>, results: &[Bound<'_, PyAny>]) -> PyResult<()> { let py: Python<'_> = request.py(); - let payload: Bound<'_, PyAny> = call_with( - ®istry::native(py, NativeAttr::FormatLoadExecutionJson)?, - &[("results", PyTuple::new(py, results)?.into_any())], - )?; - call_with( - ®istry::native(py, NativeAttr::WriteExecutionJsonOutput)?, - &[ - ("payload", payload), - ("json_output", native_field(request, "json_output")?), - ( - "json_output_path", - native_field(request, "json_output_path")?, - ), - ], - )?; + let payload: Bound<'_, PyAny> = + format_load_execution_json(py, PyTuple::new(py, results)?.into_any()) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; + write_execution_json_output( + py, + payload, + native_field(request, "json_output")?.extract::()?, + native_field(request, "json_output_path")?, + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; Ok(()) } 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 16d497cd60..269057d424 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/load/load_selection.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/load/load_selection.rs @@ -12,9 +12,11 @@ use sqlbuild_core::load_command::main::load_reference_loaders::load_reference_lo use sqlbuild_core::load_command::main::select_load_entries::select_load_entries as select_entries; use sqlbuild_core::load_command::models::{LoadEntry, LoadGraph}; +use crate::bindings::_helpers::compile_attachment::effective_config::build_effective_connection_config; use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, }; +use crate::bindings::_helpers::contracts::spec_resolution::loader_destination_parts; use crate::bindings::_helpers::native_host::adapter::AdapterHost; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::records::construct::new_record_with; @@ -127,13 +129,11 @@ impl<'py> LoadProject<'py> { } else { destination }; - let parts: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::LoaderDestinationPartsFunction)?, - &[ - ("destination", destination), - ("default_database", defaults.database.clone()), - ("default_schema", defaults.schema.clone()), - ], + let parts: Bound<'py, PyAny> = loader_destination_parts( + py, + &destination.extract::()?, + Some(defaults.database.clone()).filter(|value| !value.is_none()), + Some(defaults.schema.clone()).filter(|value| !value.is_none()), )?; let meta: Bound<'py, PyDict> = PyDict::new(py); meta.set_item(LOADER_NODE_KEY, true)?; @@ -258,13 +258,10 @@ pub(crate) fn loader_defaults<'py>( ), ) -> PyResult> { let py: Python<'py> = discovered.py(); - let connection: Bound<'py, PyDict> = call_with( - ®istry::native(py, NativeAttr::BuildEffectiveConnectionConfig)?, - &[ - ("discovered_inputs", discovered.clone()), - ("selected_target", selected_target.clone()), - ("cli_vars", cli_vars.clone()), - ], + let connection: Bound<'py, PyDict> = build_effective_connection_config( + &discovered.clone(), + Some(selected_target.clone()).filter(|value| !value.is_none()), + Some(cli_vars.clone()).filter(|value| !value.is_none()), )? .downcast_into()?; let adapter_name: Bound<'py, PyAny> = call_with( From 4aba02b4f31539cd600bb5a58685e1b2d923d2ff Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Sat, 17 Oct 2026 03:09:29 +0100 Subject: [PATCH 23/28] refactor: call test-replaced native entry points directly Call dbt init, debug and execution, profile, connection, adapter, project graph, declaration scope and fixture inference entry points directly from Rust. Tests now exercise real behaviour: dbt workspaces and profiles on disk, a stand-in dbt executable, a SQLBuild project with a real connection, and a new native fixture_inference_count work counter. The declaration-scope syntax test only checked the arguments passed to the replaced entry point and is removed; the formatting test above it still covers caller-held syntax. --- .../analysis_session/fixture_columns.rs | 33 +++- .../analysis_session/fixture_inferences.rs | 11 +- .../bindings/_helpers/analysis_session/mod.rs | 1 + .../_helpers/analysis_session/models.rs | 10 ++ .../_helpers/audit_command/command.rs | 13 +- .../_helpers/build_command/invocation.rs | 33 ++-- .../_helpers/build_command/planning.rs | 13 +- .../bindings/_helpers/clone/clone_command.rs | 23 +-- .../_helpers/command_runtime/connection.rs | 49 +++--- .../_helpers/command_runtime/invocation.rs | 35 +++-- .../_helpers/command_runtime/warehouses.rs | 16 +- .../sql_expansion_context.rs | 15 +- .../bindings/_helpers/cost_command/refresh.rs | 43 +++--- .../_helpers/dbt_command/auto_init.rs | 6 +- .../bindings/_helpers/dbt_command/debug.rs | 56 ++++--- .../src/bindings/_helpers/dbt_command/init.rs | 13 +- .../_helpers/dbt_command/init_project.rs | 6 +- .../_helpers/dbt_interop/event_stream.rs | 2 +- .../bindings/_helpers/dbt_interop/pipeline.rs | 7 +- .../_helpers/dbt_interop/pipeline_phases.rs | 46 +++--- .../_helpers/dbt_interop/pipeline_prologue.rs | 13 +- .../dbt_interop/profile_connection.rs | 2 +- .../_helpers/dbt_interop/runtime_execution.rs | 34 +++-- .../_helpers/debug_command/run_debug.rs | 24 ++- .../_helpers/format_command/selection.rs | 30 ++-- .../bindings/_helpers/init_command/command.rs | 10 +- .../bindings/_helpers/load/load_command.rs | 43 +++--- .../bindings/_helpers/load/load_selection.rs | 19 +-- .../_helpers/workspace_commands/commands.rs | 2 +- src/sqlbuild/_native.pyi | 1 + .../cli/commands/_helpers/cost/helpers.py | 11 ++ .../commands/_helpers/cost/test_refresh.py | 44 +----- .../cli/commands/_helpers/runtime/helpers.py | 11 ++ .../_helpers/runtime/test_connection.py | 46 +++--- .../cli/commands/main/dbt/_test_types.py | 2 +- .../sqlbuild/cli/commands/main/dbt/helpers.py | 34 ++++- .../cli/commands/main/dbt/test_dbt.py | 116 ++++---------- .../cli/commands/main/dbt/test_init.py | 142 ++++-------------- .../_helpers/test_fixture_planning_work.py | 13 +- .../compiler/planner/classes/helpers.py | 21 +-- .../classes/test_fixture_column_inferences.py | 10 +- .../src/sqlbuild/integrations/dbt/helpers.py | 32 +++- .../sqlbuild/integrations/dbt/test_execute.py | 61 ++++---- .../lint/test_adapter_lexical_syntax.py | 23 --- 44 files changed, 510 insertions(+), 665 deletions(-) create mode 100644 crates/sqlbuild-python/src/bindings/_helpers/analysis_session/models.rs 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 133c509880..43554846ca 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 @@ -2,7 +2,8 @@ use std::collections::HashMap; -use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; +use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; +use pyo3::sync::PyOnceLock; use pyo3::types::{PyDict, PyDictMethods, PyFrozenSet, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; use sqlbuild_analysis::assembly::analysis_session::main::projection_facts::projection_facts; @@ -10,6 +11,7 @@ use sqlbuild_analysis::assembly::analysis_session::models::ProjectionFacts; use sqlbuild_core::panics::main::native_failure::native_failure; use sqlbuild_core::text::main::python_casefold::python_casefold; +use crate::bindings::_helpers::analysis_session::models::FixtureInferenceCount; 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; @@ -22,14 +24,18 @@ use crate::bindings::types::CompilerDetach; const PROJECTION_FACTS_CONTEXT: &str = "native fixture projection facts"; const LEGACY_MODE: &str = "legacy"; +/// Fixture column inferences computed in this process. +static INFERENCES: PyOnceLock> = PyOnceLock::new(); + /// `infer_fixture_column_facts(*, query_sql, inference_profile)`: a `FixtureColumnInference`. #[pyfunction] #[pyo3(signature = (**request))] -fn infer_fixture_column_facts<'py>( +pub(crate) fn infer_fixture_column_facts<'py>( request: Option<&Bound<'py, PyDict>>, ) -> PyResult> { let request = Request::new("infer_fixture_column_facts", request)?; let py: Python<'py> = request.py(); + inference_count(py)?.borrow_mut().computed += 1; let query_sql: String = request.get("query_sql")?.extract()?; let profile: Bound<'py, PyAny> = request.get("inference_profile")?; let query: Bound<'py, PyDict> = PyDict::new(py); @@ -80,6 +86,27 @@ fn infer_fixture_column_facts<'py>( new_record(py, RecordId::FixtureColumnInference, (), Some(&arguments)) } +/// Fixture column inferences computed in this process, optionally clearing the count first. +#[pyfunction] +#[pyo3(signature = (*, reset = false))] +fn fixture_inference_count(py: Python<'_>, reset: bool) -> PyResult { + let count = inference_count(py)?; + let mut count = count.borrow_mut(); + if reset { + count.computed = 0; + } + Ok(count.computed) +} + +/// The extension-wide fixture inference counter. +fn inference_count(py: Python<'_>) -> PyResult> { + Ok(INFERENCES + .get_or_try_init(py, || Py::new(py, FixtureInferenceCount::default()))? + .bind(py) + .clone()) +} + pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { - module.add_function(wrap_pyfunction!(infer_fixture_column_facts, module)?) + module.add_function(wrap_pyfunction!(infer_fixture_column_facts, module)?)?; + module.add_function(wrap_pyfunction!(fixture_inference_count, module)?) } 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 1af733ffbd..706bc92199 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,9 +1,8 @@ //! Fixture column inference computed once per fixture body and inference profile. -use crate::bindings::_helpers::compile_driver::python_calls::call_with; +use crate::bindings::_helpers::analysis_session::fixture_columns::infer_fixture_column_facts; +use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::records::fields::field as native_field; -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}; @@ -33,13 +32,13 @@ impl FixtureColumnInferences { ) -> PyResult> { let py = query_sql.py(); let compute = || { - call_with( - ®istry::native(py, NativeAttr::InferFixtureColumnFacts)?, + infer_fixture_column_facts(Some(&keywords( + py, &[ ("query_sql", query_sql.clone()), ("inference_profile", inference_profile.clone()), ], - ) + )?)) }; if !native_field(inference_profile, "binding_catalog")?.is_none() { return compute(); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/mod.rs b/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/mod.rs index 5773febd9c..64da3a80e1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/mod.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/mod.rs @@ -1,6 +1,7 @@ pub(crate) mod fixture_columns; pub(crate) mod fixture_inferences; pub(crate) mod model_analyses; +pub(crate) mod models; pub(crate) mod nullability_rules; pub(crate) mod oracle; pub(crate) mod outcome_objects; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/models.rs b/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/models.rs new file mode 100644 index 0000000000..8fd2e82dbf --- /dev/null +++ b/crates/sqlbuild-python/src/bindings/_helpers/analysis_session/models.rs @@ -0,0 +1,10 @@ +//! Native state kept by the analysis-session bindings. + +use pyo3::pyclass; + +/// Fixture column inferences computed since the last reset. +#[pyclass] +#[derive(Debug, Default)] +pub(crate) struct FixtureInferenceCount { + pub(crate) computed: u64, +} 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 005a11b0f9..0e481fe0a2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/audit_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/audit_command/command.rs @@ -15,6 +15,7 @@ use crate::bindings::_helpers::command_runtime::invocation::resolve_reported_com use crate::bindings::_helpers::compile_driver::pipeline_entries::build_static_pipeline_result; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, keywords, within}; use crate::bindings::_helpers::compile_driver::selection_phases::compile_static_command_context; +use crate::bindings::_helpers::dbt_interop::reference_resolver::resolve_external_sql_reference_resolver; use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_entries::command_plans::build_audit_command_plan; @@ -110,15 +111,9 @@ fn compile_audits<'py>( let py: Python<'py> = request.py(); let discovered: Bound<'py, PyAny> = native_field(invocation, "discovered_inputs")?; let adapter: Bound<'py, PyAny> = native_field(invocation, "adapter")?; - let resolver: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::ResolveExternalSqlReferenceResolver)?, - &[ - ( - "project_dir", - native_field(invocation, "effective_project_dir")?, - ), - ("discovered_inputs", discovered.clone()), - ], + let resolver: Bound<'py, PyAny> = resolve_external_sql_reference_resolver( + native_field(invocation, "effective_project_dir")?, + discovered.clone(), )?; let options: Bound<'py, PyAny> = new_record_with( py, 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 f8ef98ee8d..c483955fa6 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/invocation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/invocation.rs @@ -10,10 +10,12 @@ use sqlbuild_core::build_command::models::DurationLimit; use crate::bindings::_helpers::build_command::policies::build_command_error; 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::command_runtime::connection::resolve_project_connection_config; use crate::bindings::_helpers::compile_attachment::effective_config::build_effective_settings_config; use crate::bindings::_helpers::compile_driver::discovery::discover_project_inputs; use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::contracts::spec_resolution::resolve_effective_adapter_name; use crate::bindings::_helpers::contracts::spec_resolution::resolve_target_config; use crate::bindings::_helpers::contracts::spec_resolution::resolve_target_name; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; @@ -144,28 +146,25 @@ pub(crate) fn resolve_build_context<'py>( ), )); } - let adapter_name: String = call_with( - ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, - &[ - ( - "project_config", - native_field(&discovered, "project_config")?, - ), - ("local_config", native_field(&discovered, "local_config")?), - ], + let adapter_name: String = resolve_effective_adapter_name( + &native_field(&discovered, "project_config")?, + &native_field(&discovered, "local_config")?, )? .extract()?; let adapter: Bound<'py, PyAny> = resolve_cli_adapter(&text(py, &adapter_name), &project_dir)?; let limits: Bound<'py, PyAny> = execution_limits(&discovered, &target_name, (&adapter_name, &adapter))?; - let connection_config: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::ResolveProjectConnectionConfig)?, - &[ - ("discovered_inputs", discovered.clone()), - ("project_dir", project_dir.clone()), - ("selected_target", selected_target), - ("cli_vars", native_field(request, "cli_vars")?), - ], + let connection_config: Bound<'py, PyAny> = resolve_project_connection_config( + py, + Some(keywords( + py, + &[ + ("discovered_inputs", discovered.clone()), + ("project_dir", project_dir.clone()), + ("selected_target", selected_target), + ("cli_vars", native_field(request, "cli_vars")?), + ], + )?), )?; let machine_output: bool = native_field(request, "json_output")?.is_truthy()?; let debug: bool = native_field(request, "debug")?.is_truthy()?; 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 c1f66a26c1..2363611c95 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/planning.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/planning.rs @@ -16,14 +16,14 @@ use crate::bindings::_helpers::build_command::support::{ use crate::bindings::_helpers::cli::progress_text::with_verbose_inspection; use crate::bindings::_helpers::compile_driver::pipeline::run_compile_pipeline; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, keywords}; +use crate::bindings::_helpers::dbt_interop::reference_resolver::resolve_external_sql_reference_resolver; use crate::bindings::_helpers::manifest::builder::build_manifest; use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::plan_output::plan_text::format_plan; use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::ids::RecordId; const CONNECTION_TIMING: [&str; 2] = ["connection_count", "elapsed_seconds"]; @@ -43,12 +43,9 @@ fn request_options<'py>(context: &BuildContext<'py>) -> PyResult = context.get("should_load_sources")?; let python_selectors: bool = context.request_flag("include_python")? || should_load_sources.is_truthy()?; - let resolver: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::ResolveExternalSqlReferenceResolver)?, - &[ - ("project_dir", context.get("effective_project_dir")?), - ("discovered_inputs", context.get("discovered_inputs")?), - ], + let resolver: Bound<'py, PyAny> = resolve_external_sql_reference_resolver( + context.get("effective_project_dir")?, + context.get("discovered_inputs")?, )?; new_record_with( py, 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 476da49558..4be88b9e0e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_command.rs @@ -28,8 +28,10 @@ use crate::bindings::_helpers::command_runtime::connection::resolve_target_conne use crate::bindings::_helpers::compile_driver::discovery::discover_project_inputs; use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, within}; +use crate::bindings::_helpers::contracts::spec_resolution::resolve_effective_adapter_name; use crate::bindings::_helpers::contracts::spec_resolution::resolve_target_config; use crate::bindings::_helpers::contracts::spec_resolution::resolve_target_name; +use crate::bindings::_helpers::dbt_interop::reference_resolver::resolve_external_sql_reference_resolver; use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_selection::unit_tests::reject_unit_test_selectors; @@ -169,15 +171,9 @@ fn invocation<'py>(py: Python<'py>, request: &Bound<'py, PyAny>) -> PyResult = call_with( - ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, - &[ - ( - "project_config", - native_field(&discovered, "project_config")?, - ), - ("local_config", native_field(&discovered, "local_config")?), - ], + let adapter_name: Bound<'py, PyAny> = resolve_effective_adapter_name( + &native_field(&discovered, "project_config")?, + &native_field(&discovered, "local_config")?, )?; let adapter: Bound<'py, PyAny> = resolve_cli_adapter(&adapter_name, &project_dir)?; Ok(Invocation { @@ -247,12 +243,9 @@ fn prepare<'py>( let pipeline: Bound<'py, PyAny> = within(&lifecycle(py, ("project", "project_compile"))?, |_| { progress.call_method1("on_progress", ("Preparing clone plan...",))?; - let resolver: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::ResolveExternalSqlReferenceResolver)?, - &[ - ("project_dir", invocation.project_dir.clone()), - ("discovered_inputs", invocation.discovered.clone()), - ], + let resolver: Bound<'py, PyAny> = resolve_external_sql_reference_resolver( + invocation.project_dir.clone(), + invocation.discovered.clone(), )?; let destination_connection: Bound<'py, PyAny> = new_record_with( py, 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 dd60b72550..3d69658261 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/connection.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/connection.rs @@ -17,15 +17,15 @@ use crate::bindings::_helpers::command_runtime::runtime_errors::raised; use crate::bindings::_helpers::command_runtime::warehouses::{WarehouseInputs, apply_warehouse}; use crate::bindings::_helpers::compile_attachment::effective_config::build_effective_connection_config; use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, + KeywordArguments, imported, keywords, }; +use crate::bindings::_helpers::contracts::spec_resolution::resolve_effective_adapter_name; use crate::bindings::_helpers::contracts::spec_resolution::resolve_target_config; use crate::bindings::_helpers::contracts::spec_resolution::resolve_target_name; +use crate::bindings::_helpers::dbt_interop::profile_connection::resolve_raw_dbt_profile_connection; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::native_host::third_party::ThirdPartyHost; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::NativeAttr; -use crate::bindings::_helpers::records::registry; const CONNECTION_OPTIONS: (&[&str], &[&str]) = ( &["raw_config", "project_dir", "adapter_name"], @@ -70,18 +70,21 @@ pub(crate) fn with_dbt_profile<'py>( (discovered_inputs, cli_vars): (&Bound<'py, PyAny>, &Bound<'py, PyAny>), ) -> PyResult> { let py: Python<'py> = raw_config.py(); - let profile: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::ResolveRawDbtProfileConnection)?, - &[ - ("raw_config", config.clone().into_any()), - ("project_dir", project_dir.clone()), - ("adapter_name", adapter_name.clone()), - ( - "project_config", - native_field(discovered_inputs, "project_config")?, - ), - ("cli_vars", cli_vars.clone()), - ], + let profile: Bound<'py, PyAny> = resolve_raw_dbt_profile_connection( + py, + Some(keywords( + py, + &[ + ("raw_config", config.clone().into_any()), + ("project_dir", project_dir.clone()), + ("adapter_name", adapter_name.clone()), + ( + "project_config", + native_field(discovered_inputs, "project_config")?, + ), + ("cli_vars", cli_vars.clone()), + ], + )?), )?; if profile.is_none() { return Ok(config); @@ -242,7 +245,6 @@ pub(crate) fn project_connection_config<'py>( (project_dir, selected_target): (&Bound<'py, PyAny>, &Bound<'py, PyAny>), (cli_vars, require_connection): (&Bound<'py, PyAny>, bool), ) -> PyResult> { - let py: Python<'py> = project_dir.py(); if require_connection { require_configured_connection(discovered_inputs, project_dir, selected_target)?; } @@ -252,18 +254,9 @@ pub(crate) fn project_connection_config<'py>( Some(cli_vars.clone()).filter(|value| !value.is_none()), )? .cast_into()?; - let adapter_name: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, - &[ - ( - "project_config", - native_field(discovered_inputs, "project_config")?, - ), - ( - "local_config", - native_field(discovered_inputs, "local_config")?, - ), - ], + let adapter_name: Bound<'py, PyAny> = resolve_effective_adapter_name( + &native_field(discovered_inputs, "project_config")?, + &native_field(discovered_inputs, "local_config")?, )?; connection_config( &raw_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 e5b01ad927..f6fa2e6d13 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/invocation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/invocation.rs @@ -4,9 +4,11 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::PyBool; use crate::bindings::_helpers::command_runtime::adapters::resolve_cli_adapter; +use crate::bindings::_helpers::command_runtime::connection::resolve_project_connection_config; use crate::bindings::_helpers::compile_driver::discovery::discover_project_inputs; use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::contracts::spec_resolution::resolve_effective_adapter_name; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; @@ -34,26 +36,23 @@ pub(crate) fn resolve_adapter_connection_context<'py>( require_connection: bool, ) -> PyResult> { let py: Python<'py> = discovered.py(); - let adapter_name: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, - &[ - ( - "project_config", - native_field(discovered, "project_config")?, - ), - ("local_config", native_field(discovered, "local_config")?), - ], + let adapter_name: Bound<'py, PyAny> = resolve_effective_adapter_name( + &native_field(discovered, "project_config")?, + &native_field(discovered, "local_config")?, )?; let adapter: Bound<'py, PyAny> = resolve_cli_adapter(&adapter_name, project_dir)?; - let connection_config: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::ResolveProjectConnectionConfig)?, - &[ - ("discovered_inputs", discovered.clone()), - ("project_dir", project_dir.clone()), - ("selected_target", selected_target), - ("cli_vars", cli_vars), - ("require_connection", boolean(py, require_connection)), - ], + let connection_config: Bound<'py, PyAny> = resolve_project_connection_config( + py, + Some(keywords( + py, + &[ + ("discovered_inputs", discovered.clone()), + ("project_dir", project_dir.clone()), + ("selected_target", selected_target), + ("cli_vars", cli_vars), + ("require_connection", boolean(py, require_connection)), + ], + )?), )?; new_record_with( py, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/warehouses.rs b/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/warehouses.rs index 31feb1c9b8..88e4d0e64a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/warehouses.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/warehouses.rs @@ -24,6 +24,7 @@ use crate::bindings::_helpers::command_runtime::runtime_errors::{ use crate::bindings::_helpers::command_runtime::warehouse_scope::current_command_warehouse_scope; use crate::bindings::_helpers::compile_attachment::effective_config::build_effective_runtime_config; use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::contracts::spec_resolution::resolve_effective_adapter_name; use crate::bindings::_helpers::contracts::spec_resolution::resolve_target_name; use crate::bindings::_helpers::discovery::configuration::discover_project_configuration; use crate::bindings::_helpers::model_config::template_data::expanded_template_data; @@ -31,8 +32,7 @@ use crate::bindings::_helpers::native_host::adapter::AdapterHost; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::ids::RecordId; /// The `WarehouseGroup` whose warehouse is a target's `warehouses.build`; any other reads `query`. const BUILD_GROUP: &str = "build"; @@ -287,15 +287,9 @@ pub(crate) 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( - ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, - &[ - ( - "project_config", - native_field(&discovered, "project_config")?, - ), - ("local_config", native_field(&discovered, "local_config")?), - ], + let adapter_name: Bound<'_, PyAny> = resolve_effective_adapter_name( + &native_field(&discovered, "project_config")?, + &native_field(&discovered, "local_config")?, )?; if !session_warehouse_support(py, &adapter_name, &project_dir)? { return Err(unsupported(py, &cli_warehouse, &adapter_name)); 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 3484f387e3..a78720c431 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,11 +4,12 @@ use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::pyfunction; use pyo3::types::{PyBool, PyDict, PyDictMethods}; +use crate::bindings::_helpers::compile_attachment::declaration_scope::build_declaration_scope; use crate::bindings::_helpers::compile_attachment::declaration_scope::rebind_declaration_scope; use crate::bindings::_helpers::compile_attachment::effective_config::build_effective_vars; use crate::bindings::_helpers::compile_driver::discovery::discover_project_inputs; +use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; use crate::bindings::_helpers::compile_driver::python_calls::keywords; -use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::contracts::spec_resolution::resolve_effective_adapter_name; use crate::bindings::_helpers::contracts::spec_resolution::resolve_effective_collection_rendering; use crate::bindings::_helpers::native_host::host; @@ -36,13 +37,11 @@ fn declaration_scope<'py>( return Ok(rebound); } } - call_with( - ®istry::native(py, NativeAttr::BuildDeclarationScope)?, - &[ - ("discovered_inputs", discovered.clone()), - ("loaded_macros", loaded_macros), - ("sql_lexical_syntax", sql_lexical_syntax), - ], + build_declaration_scope( + &discovered.clone(), + &loaded_macros, + &sql_lexical_syntax, + None, ) } 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 9b552fc302..5306dc5e70 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_command/refresh.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_command/refresh.rs @@ -10,9 +10,12 @@ use sqlbuild_core::cost_command::main::accepts_cost_refresh::accepts_cost_refres use sqlbuild_core::cost_command::models::CostCoverage; use crate::bindings::_helpers::command_runtime::adapters::resolve_cli_adapter; +use crate::bindings::_helpers::command_runtime::connection::resolve_project_connection_config; +use crate::bindings::_helpers::compile_attachment::effective_config::build_effective_target_config; use crate::bindings::_helpers::compile_driver::discovery::discover_project_inputs; use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::contracts::spec_resolution::resolve_effective_adapter_name; use crate::bindings::_helpers::cost_report::functions::read_cost_run; use crate::bindings::_helpers::cost_report::functions::resolve_cost_run; use crate::bindings::_helpers::cost_report::functions::write_cost_run; @@ -20,8 +23,6 @@ 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::fields::field as native_field; -use crate::bindings::_helpers::records::ids::NativeAttr; -use crate::bindings::_helpers::records::registry; fn coverage(summary: &Bound<'_, PyAny>) -> PyResult { Ok(CostCoverage { @@ -114,22 +115,13 @@ fn refresh(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult<()> { py, Some(keywords(py, &[("project_dir", project_dir.clone())])?), )?; - let target_config: Bound<'_, PyAny> = call_with( - ®istry::native(py, NativeAttr::BuildEffectiveTargetConfig)?, - &[ - ("discovered_inputs", discovered.clone()), - ("selected_target", target_name.clone()), - ], + let target_config: Bound<'_, PyAny> = build_effective_target_config( + &discovered.clone(), + Some(target_name.clone()).filter(|value| !value.is_none()), )?; - let adapter_name: Bound<'_, PyAny> = call_with( - ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, - &[ - ( - "project_config", - native_field(&discovered, "project_config")?, - ), - ("local_config", native_field(&discovered, "local_config")?), - ], + let adapter_name: Bound<'_, PyAny> = resolve_effective_adapter_name( + &native_field(&discovered, "project_config")?, + &native_field(&discovered, "local_config")?, )?; let adapter: Bound<'_, PyAny> = resolve_cli_adapter(&adapter_name, &project_dir)?; if !cost_aware(&adapter)? { @@ -143,13 +135,16 @@ fn refresh(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult<()> { { return Ok(()); } - let connection_config: Bound<'_, PyAny> = call_with( - ®istry::native(py, NativeAttr::ResolveProjectConnectionConfig)?, - &[ - ("discovered_inputs", discovered), - ("project_dir", project_dir.clone()), - ("selected_target", target_name), - ], + let connection_config: Bound<'_, PyAny> = resolve_project_connection_config( + py, + Some(keywords( + py, + &[ + ("discovered_inputs", discovered), + ("project_dir", project_dir.clone()), + ("selected_target", target_name), + ], + )?), )?; let run_id: Bound<'_, PyAny> = native_field(&record, "run_id")?; let ledger: Bound<'_, PyAny> = project_dir 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 aa6f2d337e..19e2ab6958 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 @@ -12,6 +12,7 @@ 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; +use crate::bindings::_helpers::dbt_command::init_project::build_dbt_init_project; use crate::bindings::_helpers::dbt_command::support::{color_enabled, flag, path, stream}; use crate::bindings::_helpers::records::construct::new_record_with; @@ -121,10 +122,7 @@ pub(crate) fn ensure_twin_project<'py>( ("progress_callbacks", callbacks), ], )?; - let result: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::BuildDbtInitProject)?, - &[("request", request)], - )?; + let result: Bound<'py, PyAny> = build_dbt_init_project(&request)?; let project_file: String = native_field(&result, "project_file")?.str()?.to_string(); progress_stream.call_method1( "write", 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 3c63ed14d7..a3158e1ed4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/debug.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/debug.rs @@ -3,11 +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; +use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::dbt_command::auto_init::args_tuple; use crate::bindings::_helpers::dbt_command::support::{color_enabled, flag, status, stream}; -use crate::bindings::_helpers::records::ids::NativeAttr; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::dbt_interop::pipeline::debug_dbt_from_project; +use crate::bindings::_helpers::debug_command::run_debug::run_debug_command; /// Run `body` under a stderr status line that is always closed; returns its exit code. fn with_status( @@ -33,32 +33,40 @@ pub(crate) fn run_dbt_debug( let py: Python<'_> = project_dir.py(); let use_color: bool = !no_color && color_enabled(py)?; let dbt_code: i64 = with_status(py, ("Running dbt debug...", use_color), || { - call_with( - ®istry::native(py, NativeAttr::DebugDbtFromProject)?, - &[ - ("project_dir", project_dir.clone()), - ("args", args_tuple(py, args)?), - ("stdout_stream", stream(py, "stdout")?), - ("stderr_stream", stream(py, "stderr")?), - ], - )? + debug_dbt_from_project( + py, + Some(keywords( + py, + &[ + ("project_dir", project_dir.clone()), + ("args", args_tuple(py, args)?), + ("stdout_stream", stream(py, "stdout")?), + ("stderr_stream", stream(py, "stderr")?), + ], + )?), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))? .extract() })?; stream(py, "stdout")?.call_method1("write", ("\n",))?; let sqlbuild_code: i64 = with_status(py, ("Running SQLBuild diagnostics...", use_color), || { - call_with( - ®istry::native(py, NativeAttr::RunDebugCommand)?, - &[ - ("project_dir", project_dir.clone()), - ("no_color", flag(py, no_color)), - ( - "no_connection", - flag(py, args.iter().any(|arg| arg == NO_CONNECTION_OPTION)), - ), - ("json_output", flag(py, false)), - ], - )? + run_debug_command( + py, + Some(keywords( + py, + &[ + ("project_dir", project_dir.clone()), + ("no_color", flag(py, no_color)), + ( + "no_connection", + flag(py, args.iter().any(|arg| arg == NO_CONNECTION_OPTION)), + ), + ("json_output", flag(py, false)), + ], + )?), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))? .extract() })?; Ok(i32::from(dbt_code != 0 || sqlbuild_code != 0)) 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 63844e5a4f..5be9f9b1a7 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/init.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/init.rs @@ -12,6 +12,8 @@ use sqlbuild_core::dbt_command::main::render_dbt_init_summary::render_dbt_init_s use sqlbuild_core::dbt_command::models::DbtInitSummary; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::dbt_command::init_project::build_dbt_init_project; +use crate::bindings::_helpers::dbt_command::init_project::validate_dbt_init_project; use crate::bindings::_helpers::dbt_command::support::{color_enabled, flag, path, stream}; use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; @@ -118,14 +120,9 @@ fn run_dbt_init_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult< ("progress_callbacks", callbacks), ], )?; - call_with( - ®istry::native(py, NativeAttr::ValidateDbtInitProject)?, - &[("request", init_request.clone())], - )?; - let result: Bound<'_, PyAny> = call_with( - ®istry::native(py, NativeAttr::BuildDbtInitProject)?, - &[("request", init_request)], - )?; + validate_dbt_init_project(&init_request.clone()) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; + let result: Bound<'_, PyAny> = build_dbt_init_project(&init_request)?; let style: CliStyle = CliStyle { use_color }; let stdout: Bound<'_, PyAny> = stream(py, "stdout")?; if native_field(request, "dry_run")?.is_truthy()? { 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 81cfed492b..56fc6e332c 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 @@ -44,7 +44,9 @@ struct InitPaths<'py> { /// Create or render a minimal SQLBuild project from a dbt project. #[pyfunction] #[pyo3(signature = (*, request))] -fn build_dbt_init_project<'py>(request: &Bound<'py, PyAny>) -> PyResult> { +pub(crate) fn build_dbt_init_project<'py>( + request: &Bound<'py, PyAny>, +) -> PyResult> { let py: Python<'py> = request.py(); let cwd: Bound<'py, PyAny> = native_field(request, "cwd")?; let paths: InitPaths<'py> = init_paths(request)?; @@ -169,7 +171,7 @@ fn build_dbt_init_project<'py>(request: &Bound<'py, PyAny>) -> PyResult) -> Result<(), PyErr> { +pub(crate) fn validate_dbt_init_project(request: &Bound<'_, PyAny>) -> Result<(), PyErr> { let paths: InitPaths<'_> = init_paths(request)?; let metadata: DbtProjectMetadata = load_dbt_project_metadata(&paths.dbt_project_dir)?; let profile_name: Bound<'_, PyAny> = profile_name(request, &metadata)?; 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 efd43a63a3..b005bba6bb 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 @@ -77,7 +77,7 @@ struct Output<'py> { /// Run dbt and render SQLBuild-styled rows from JSON events; returns the exit code and results. #[pyfunction] #[pyo3(signature = (**options))] -fn execute_dbt_json_event_stream<'py>( +pub(crate) fn execute_dbt_json_event_stream<'py>( py: Python<'py>, options: Option>, ) -> PyResult> { 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 453b46f13d..bb88d473fc 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/pipeline.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/pipeline.rs @@ -241,7 +241,7 @@ fn execute_dbt_interop_from_project(request: &Bound<'_, PyAny>) -> PyResult /// Build an interop plan from actual project files, dbt artifacts and warehouse facts. #[pyfunction] #[pyo3(signature = (**options))] -fn plan_dbt_interop_from_project<'py>( +pub(crate) fn plan_dbt_interop_from_project<'py>( py: Python<'py>, options: Option>, ) -> PyResult> { @@ -324,7 +324,10 @@ fn plan_dbt_interop_from_project<'py>( /// Run dbt debug using SQLBuild's resolved dbt configuration. #[pyfunction] #[pyo3(signature = (**options))] -fn debug_dbt_from_project(py: Python<'_>, options: Option>) -> PyResult { +pub(crate) fn debug_dbt_from_project( + py: Python<'_>, + options: Option>, +) -> PyResult { let args = KeywordArguments::read(py, "debug_dbt_from_project", options, DEBUG_OPTIONS)?; let discovered = discover_project_inputs( py, 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 e964ede527..b602a7dc65 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 @@ -14,6 +14,7 @@ use crate::bindings::_helpers::dbt_interop::pipeline_prologue::progress; use crate::bindings::_helpers::dbt_interop::plan::render_dbt_interop_plan; use crate::bindings::_helpers::dbt_interop::plan_output::build_sqlbuild_plan_output; use crate::bindings::_helpers::dbt_interop::plan_output::{connection_config, connection_hooks}; +use crate::bindings::_helpers::dbt_interop::runtime_execution::execute_dbt_commands; use crate::bindings::_helpers::dbt_interop::sqlbuild_work::execute_dbt_sqlbuild_work; use crate::bindings::_helpers::records::construct::{new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; @@ -134,27 +135,30 @@ fn execute_dbt_without_state_tracking<'py>( let args = KeywordArguments::read(py, "execute_dbt_without_state_tracking", options, OPTIONS)?; let request = args.value("request")?; let invocation = args.value("invocation")?; - call_with( - ®istry::native(py, NativeAttr::ExecuteDbtCommands)?, - &[ - ("runner", native_field(&invocation, "runner")?), - ("options", native_field(&invocation, "dbt_options")?), - ("merged_argv", args.value("merged_dbt_argv")?), - ( - "progress_stream", - native_field(&invocation, "output_stream")?, - ), - ( - "stdout_stream", - native_field(&invocation, "dbt_output_stream")?, - ), - ("use_color", native_field(&request, "use_color")?), - ( - "skip_message", - text(py, "Skipping dbt: no dbt work selected."), - ), - ("on_progress", native_field(&request, "on_progress")?), - ], + execute_dbt_commands( + py, + Some(keywords( + py, + &[ + ("runner", native_field(&invocation, "runner")?), + ("options", native_field(&invocation, "dbt_options")?), + ("merged_argv", args.value("merged_dbt_argv")?), + ( + "progress_stream", + native_field(&invocation, "output_stream")?, + ), + ( + "stdout_stream", + native_field(&invocation, "dbt_output_stream")?, + ), + ("use_color", native_field(&request, "use_color")?), + ( + "skip_message", + text(py, "Skipping dbt: no dbt work selected."), + ), + ("on_progress", native_field(&request, "on_progress")?), + ], + )?), ) } 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 4bb5b3f5ef..94ba27b93e 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 @@ -4,6 +4,7 @@ use crate::bindings::_helpers::compile_driver::discovery::discover_project_input use crate::bindings::_helpers::compile_driver::project::build_compiled_project; use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::contracts::spec_resolution::resolve_effective_adapter_name; use crate::bindings::_helpers::dbt_interop::cli::route_dbt_interop_args; use crate::bindings::_helpers::dbt_interop::graph::build_dbt_combined_graph; use crate::bindings::_helpers::dbt_interop::manifest::load_dbt_manifest_index; @@ -181,15 +182,9 @@ fn compile_dbt_interop_project<'py>( let start = Instant::now(); progress(&callback, "Compiling SQLBuild project...")?; let discovered = args.value("discovered_inputs")?; - let name = call_with( - ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, - &[ - ( - "project_config", - native_field(&discovered, "project_config")?, - ), - ("local_config", native_field(&discovered, "local_config")?), - ], + let name = resolve_effective_adapter_name( + &native_field(&discovered, "project_config")?, + &native_field(&discovered, "local_config")?, )?; let adapter = interop_adapter(&name, &args.value("project_dir")?)?; let resolver = Bound::new( 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 fa51865871..7971187131 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 @@ -103,7 +103,7 @@ fn profile_connection<'py>( /// Resolve a raw connection config if it references a dbt profile. #[pyfunction] #[pyo3(signature = (**options))] -fn resolve_raw_dbt_profile_connection<'py>( +pub(crate) fn resolve_raw_dbt_profile_connection<'py>( py: Python<'py>, options: Option>, ) -> PyResult> { 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 4ec255e364..b52995fcee 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 @@ -5,6 +5,7 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ }; use crate::bindings::_helpers::dbt_events::stream::render_dbt_node_result; use crate::bindings::_helpers::dbt_interop::cli::parse_dbt_ls_json_lines; +use crate::bindings::_helpers::dbt_interop::event_stream::execute_dbt_json_event_stream; use crate::bindings::_helpers::dbt_interop::execution::dbt_execution_ls_argv; use crate::bindings::_helpers::dbt_interop::runtime_results::{append_missing, result_ids}; use crate::bindings::_helpers::records::construct::new_record_with; @@ -105,7 +106,7 @@ fn progress(arguments: &KeywordArguments<'_>, message: &str) -> PyResult<()> { /// Execute selected dbt work and backfill results absent from its JSON event stream. #[pyfunction] #[pyo3(signature = (**options))] -fn execute_dbt_commands<'py>( +pub(crate) fn execute_dbt_commands<'py>( py: Python<'py>, options: Option>, ) -> PyResult> { @@ -178,19 +179,24 @@ fn execute_dbt_commands<'py>( if target.is_none() && !project_dir.is_none() { target = project_dir.call_method1("__truediv__", ("target",))?; } - let (returncode, streamed): (Bound<'py, PyAny>, Bound<'py, PyAny>) = call_with( - ®istry::native(py, NativeAttr::ExecuteDbtJsonEventStream)?, - &[ - ("argv", argv), - ("cwd", project_dir), - ("stream", arguments.value("stdout_stream")?), - ("use_color", arguments.value("use_color")?), - ("target_path", target.clone()), - ("display_total", total.clone()), - ("on_node_result", arguments.value("on_node_result")?), - ], - )? - .extract()?; + let (returncode, streamed): (Bound<'py, PyAny>, Bound<'py, PyAny>) = + execute_dbt_json_event_stream( + py, + Some(keywords( + py, + &[ + ("argv", argv), + ("cwd", project_dir), + ("stream", arguments.value("stdout_stream")?), + ("use_color", arguments.value("use_color")?), + ("target_path", target.clone()), + ("display_total", total.clone()), + ("on_node_result", arguments.value("on_node_result")?), + ], + )?), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))? + .extract()?; progress(&arguments, "Finalizing dbt run...")?; let streamed_ids = result_ids(&streamed)?; let mut filtered: Vec> = Vec::new(); 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 e1bb0b6f23..869bc16d7b 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 @@ -36,11 +36,11 @@ use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, imported, within, }; +use crate::bindings::_helpers::contracts::spec_resolution::resolve_effective_adapter_name; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; use crate::bindings::_helpers::records::construct::record_class; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::ids::RecordId; const DEBUG_OPTIONS: (&[&str], &[&str]) = ( &[], @@ -271,18 +271,9 @@ fn build_report( )?), )?; let files: DebugConfigFiles = debug_config_files(project_dir); - let adapter_name: Bound<'_, PyAny> = call_with( - ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, - &[ - ( - "project_config", - native_field(&discovered_inputs, "project_config")?, - ), - ( - "local_config", - native_field(&discovered_inputs, "local_config")?, - ), - ], + let adapter_name: Bound<'_, PyAny> = resolve_effective_adapter_name( + &native_field(&discovered_inputs, "project_config")?, + &native_field(&discovered_inputs, "local_config")?, )?; let adapter: Bound<'_, PyAny> = resolve_cli_adapter(&adapter_name, &project_dir_object)?; let context: DebugContext<'_> = DebugContext { @@ -379,7 +370,10 @@ fn build_report( /// `sqb debug`: validate project config and the active connection; `1` when a check failed. #[pyfunction] #[pyo3(signature = (**options))] -fn run_debug_command(py: Python<'_>, options: Option>) -> PyResult { +pub(crate) fn run_debug_command( + py: Python<'_>, + options: Option>, +) -> PyResult { let arguments: KeywordArguments<'_> = KeywordArguments::read(py, "run_debug_command", options, DEBUG_OPTIONS)?; let project_dir: Bound<'_, PyAny> = arguments.value("project_dir")?; 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 4053a4e94b..9209dcfcd0 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/format_command/selection.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/format_command/selection.rs @@ -16,8 +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::discovery::discover_project_inputs; +use crate::bindings::_helpers::compile_driver::project::build_project_graph; use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, within}; +use crate::bindings::_helpers::contracts::spec_resolution::resolve_effective_adapter_name; use crate::bindings::_helpers::planner_selection::selectors::select_project_graph; use crate::bindings::_helpers::planner_selection::unit_tests::reject_unit_test_selectors; use crate::bindings::_helpers::records::construct::{enum_member, new_record_with}; @@ -76,16 +78,9 @@ fn discovered_adapter<'py>( project_dir: &Bound<'py, PyAny>, discovered: &Bound<'py, PyAny>, ) -> PyResult> { - let py: Python<'py> = project_dir.py(); - let adapter_name: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, - &[ - ( - "project_config", - native_field(discovered, "project_config")?, - ), - ("local_config", native_field(discovered, "local_config")?), - ], + let adapter_name: Bound<'py, PyAny> = resolve_effective_adapter_name( + &native_field(discovered, "project_config")?, + &native_field(discovered, "local_config")?, )?; resolve_cli_adapter(&adapter_name, project_dir) } @@ -159,12 +154,15 @@ fn model_paths( if let Some(paths) = exact_model_paths(py, scope, (select, exclude))? { return Ok(paths); } - let graph: Bound<'_, PyAny> = call_with( - ®istry::native(py, NativeAttr::BuildProjectGraph)?, - &[ - ("discovered_inputs", scope.discovered.clone()), - ("adapter", scope.adapter.clone()), - ], + let graph: Bound<'_, PyAny> = build_project_graph( + py, + Some(keywords( + py, + &[ + ("discovered_inputs", scope.discovered.clone()), + ("adapter", scope.adapter.clone()), + ], + )?), )?; let keys: Bound<'_, PyAny> = select_project_graph( &graph.clone(), 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 3323d9c927..93351b790d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/init_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/init_command/command.rs @@ -13,12 +13,13 @@ use sqlbuild_core::init_command::main::init_project_name::init_project_name; use sqlbuild_core::init_command::main::init_project_toml::init_project_toml; 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::compile_driver::python_calls::{imported, keywords}; 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::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr}; +use crate::bindings::_helpers::records::ids::ErrorId; use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::workspace_commands::commands::update_sqlbuild_skills; const EXISTS_MESSAGE: &str = "sqlbuild_project.toml already exists in this directory"; @@ -53,10 +54,7 @@ fn run_init_command(py: Python<'_>, project_dir: Option>) -> Py init_project_name(&native_field(&base_dir, "name")?.extract::()?); let base_path: PathBuf = base_dir.extract()?; scaffold_blank_project(py, &base_path, &init_project_toml(&project_name))?; - call_with( - ®istry::native(py, NativeAttr::UpdateSqlbuildSkills)?, - &[("project_dir", base_dir)], - )?; + update_sqlbuild_skills(py, Some(keywords(py, &[("project_dir", base_dir)])?))?; let style: CliStyle = CliStyle { use_color: stdout_supports_color(py)?, }; 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 19e7541dc0..4d4c85f20a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/load/load_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/load/load_command.rs @@ -17,11 +17,14 @@ 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::command_runtime::connection::resolve_project_connection_config; use crate::bindings::_helpers::compile_attachment::effective_config::build_effective_runtime_config; use crate::bindings::_helpers::compile_attachment::effective_config::build_effective_settings_config; +use crate::bindings::_helpers::compile_attachment::effective_config::build_effective_target_config; use crate::bindings::_helpers::compile_driver::discovery::discover_project_inputs; use crate::bindings::_helpers::compile_driver::python_calls::call_with; use crate::bindings::_helpers::compile_driver::python_calls::keywords; +use crate::bindings::_helpers::contracts::spec_resolution::resolve_effective_adapter_name; use crate::bindings::_helpers::cursor::overrides::{cursor_integer, cursor_timestamp}; use crate::bindings::_helpers::load::load_progress::{ LoadCallbackRelay, LoadProgressReporter, load_result_text, @@ -91,12 +94,9 @@ fn invocation<'py>(py: Python<'py>, request: &Bound<'py, PyAny>) -> PyResult = native_field(request, "exclude")?; reject_unit_test_selectors(&select.clone(), &exclude.clone(), &discovered.clone())?; let selected_target: Bound<'py, PyAny> = native_field(request, "selected_target")?; - let target_config: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::BuildEffectiveTargetConfig)?, - &[ - ("discovered_inputs", discovered.clone()), - ("selected_target", selected_target.clone()), - ], + let target_config: Bound<'py, PyAny> = build_effective_target_config( + &discovered.clone(), + Some(selected_target.clone()).filter(|value| !value.is_none()), )?; let defaults: LoaderDefaults<'py> = loader_defaults( &discovered, @@ -269,25 +269,22 @@ fn prepare<'py>( ) -> PyResult> { let py: Python<'py> = request.py(); let discovered: &Bound<'py, PyAny> = &invocation.discovered; - let adapter_name: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, - &[ - ( - "project_config", - native_field(discovered, "project_config")?, - ), - ("local_config", native_field(discovered, "local_config")?), - ], + let adapter_name: Bound<'py, PyAny> = resolve_effective_adapter_name( + &native_field(discovered, "project_config")?, + &native_field(discovered, "local_config")?, )?; let adapter: Bound<'py, PyAny> = resolve_cli_adapter(&adapter_name, &invocation.project_dir)?; - let connection_config: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::ResolveProjectConnectionConfig)?, - &[ - ("discovered_inputs", discovered.clone()), - ("project_dir", invocation.project_dir.clone()), - ("selected_target", native_field(request, "selected_target")?), - ("cli_vars", native_field(request, "cli_vars")?), - ], + let connection_config: Bound<'py, PyAny> = resolve_project_connection_config( + py, + Some(keywords( + py, + &[ + ("discovered_inputs", discovered.clone()), + ("project_dir", invocation.project_dir.clone()), + ("selected_target", native_field(request, "selected_target")?), + ("cli_vars", native_field(request, "cli_vars")?), + ], + )?), )?; check_reference_targets(invocation, (&adapter, &connection_config))?; let runtime: (Bound<'py, PyAny>, Bound<'py, PyAny>, Bound<'py, PyAny>) = 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 269057d424..1f623b4a63 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/load/load_selection.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/load/load_selection.rs @@ -13,15 +13,14 @@ use sqlbuild_core::load_command::main::select_load_entries::select_load_entries use sqlbuild_core::load_command::models::{LoadEntry, LoadGraph}; use crate::bindings::_helpers::compile_attachment::effective_config::build_effective_connection_config; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported}; use crate::bindings::_helpers::contracts::spec_resolution::loader_destination_parts; +use crate::bindings::_helpers::contracts::spec_resolution::resolve_effective_adapter_name; use crate::bindings::_helpers::native_host::adapter::AdapterHost; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; const LOADER_NODE_KEY: &str = "sqlbuild_loader_node"; @@ -264,15 +263,9 @@ pub(crate) fn loader_defaults<'py>( Some(cli_vars.clone()).filter(|value| !value.is_none()), )? .downcast_into()?; - let adapter_name: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, - &[ - ( - "project_config", - native_field(discovered, "project_config")?, - ), - ("local_config", native_field(discovered, "local_config")?), - ], + let adapter_name: Bound<'py, PyAny> = resolve_effective_adapter_name( + &native_field(discovered, "project_config")?, + &native_field(discovered, "local_config")?, )?; let duckdb: Bound<'py, PyAny> = host::entry(py, AdapterHost::BuiltinAdapter)?.getattr("DUCKDB")?; 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 267ef7aa5c..0ff0984540 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/workspace_commands/commands.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/workspace_commands/commands.rs @@ -101,7 +101,7 @@ fn skill_request(py: Python<'_>, project_dir: PathBuf) -> PyResult( +pub(crate) fn update_sqlbuild_skills<'py>( py: Python<'py>, options: Option>, ) -> PyResult> { diff --git a/src/sqlbuild/_native.pyi b/src/sqlbuild/_native.pyi index 8cbcaa2e99..f6103ffdbe 100644 --- a/src/sqlbuild/_native.pyi +++ b/src/sqlbuild/_native.pyi @@ -5196,6 +5196,7 @@ def map_expanded_offset( def infer_fixture_column_facts( *, query_sql: str, inference_profile: ExpressionInferenceProfile ) -> FixtureColumnInference | None: ... +def fixture_inference_count(*, reset: bool = False) -> int: ... def find_macro_call_names(sql: str, /) -> tuple[str, ...]: ... def build_native_declaration_contexts( *, diff --git a/tests/unit/src/sqlbuild/cli/commands/_helpers/cost/helpers.py b/tests/unit/src/sqlbuild/cli/commands/_helpers/cost/helpers.py index 1649470087..2cb12be74b 100644 --- a/tests/unit/src/sqlbuild/cli/commands/_helpers/cost/helpers.py +++ b/tests/unit/src/sqlbuild/cli/commands/_helpers/cost/helpers.py @@ -34,3 +34,14 @@ def build_cost_finalization( render=render, had_executable_work=had_executable_work, ) + + +def write_snowflake_project(project_dir: Path) -> None: + """Write a Snowflake project with a `dev` target whose connection uses `ANALYTICS`.""" + + _ = (project_dir / "sqlbuild_project.toml").write_text( + 'name = "shop"\nadapter = "snowflake"\n\n' + '[connection]\naccount = "acct"\ndatabase = "ANALYTICS"\n\n' + "[targets.dev]\n", + encoding="utf-8", + ) diff --git a/tests/unit/src/sqlbuild/cli/commands/_helpers/cost/test_refresh.py b/tests/unit/src/sqlbuild/cli/commands/_helpers/cost/test_refresh.py index 19883313a2..77547cc6a8 100644 --- a/tests/unit/src/sqlbuild/cli/commands/_helpers/cost/test_refresh.py +++ b/tests/unit/src/sqlbuild/cli/commands/_helpers/cost/test_refresh.py @@ -4,7 +4,6 @@ from datetime import UTC, datetime from decimal import Decimal from pathlib import Path -from types import SimpleNamespace from typing import Any import pytest @@ -22,6 +21,7 @@ CostRefreshDegradationTestCase, CostRefreshTestCase, ) +from tests.unit.src.sqlbuild.cli.commands._helpers.cost.helpers import write_snowflake_project from tests.unit.src.sqlbuild.cost.helpers import build_cost_run_record @@ -86,25 +86,11 @@ def test_given_pending_run_when_showing_detail_then_persisted_summary_is_refresh project_dir=tmp_path, record=replace(record, cost=replace(record.cost, status=test_case.initial_status)), ) - _ = (tmp_path / "sqlbuild_project.toml").write_text( - 'name = "shop"\nadapter = "snowflake"\n', encoding="utf-8" - ) - monkeypatch.setattr( - "sqlbuild._native.resolve_effective_adapter_name", - lambda **kwargs: "snowflake", - ) + write_snowflake_project(tmp_path) monkeypatch.setattr( "sqlbuild.adapter.discovery.main.resolve_adapter.resolve_adapter", lambda **kwargs: _RefreshingCostAdapter(), ) - monkeypatch.setattr( - "sqlbuild._native.resolve_project_connection_config", - lambda **kwargs: {"database": "ANALYTICS"}, - ) - monkeypatch.setattr( - "sqlbuild._native.build_effective_target_config", - lambda **kwargs: SimpleNamespace(database=None), - ) request: CostCommandRequest = CostCommandRequest( project_dir=tmp_path, selector=test_case.selector, @@ -189,22 +175,11 @@ def test_given_useful_partial_when_refresh_degrades_then_persisted_summary_is_pr observed_statement_count=test_case.candidate_observed_statement_count, query_count=test_case.candidate_query_count, ) - monkeypatch.setattr( - "sqlbuild._native.discover_project_inputs", - lambda **kwargs: SimpleNamespace(project_config=object(), local_config=object()), - ) - monkeypatch.setattr( - "sqlbuild._native.resolve_effective_adapter_name", - lambda **kwargs: "snowflake", - ) + write_snowflake_project(tmp_path) monkeypatch.setattr( "sqlbuild.adapter.discovery.main.resolve_adapter.resolve_adapter", lambda **kwargs: _StaticCostAdapter(candidate), ) - monkeypatch.setattr( - "sqlbuild._native.resolve_project_connection_config", - lambda **kwargs: {}, - ) request: CostCommandRequest = CostCommandRequest( project_dir=tmp_path, selector="run-partial", @@ -272,22 +247,11 @@ def test_given_persisted_ledger_failure_when_refresh_has_no_coverage_then_failur observed_statement_count=test_case.candidate_observed_statement_count, query_count=test_case.candidate_query_count, ) - monkeypatch.setattr( - "sqlbuild._native.discover_project_inputs", - lambda **kwargs: SimpleNamespace(project_config=object(), local_config=object()), - ) - monkeypatch.setattr( - "sqlbuild._native.resolve_effective_adapter_name", - lambda **kwargs: "snowflake", - ) + write_snowflake_project(tmp_path) monkeypatch.setattr( "sqlbuild.adapter.discovery.main.resolve_adapter.resolve_adapter", lambda **kwargs: _StaticCostAdapter(candidate), ) - monkeypatch.setattr( - "sqlbuild._native.resolve_project_connection_config", - lambda **kwargs: {}, - ) request: CostCommandRequest = CostCommandRequest( project_dir=tmp_path, selector="run-ledger-failure", diff --git a/tests/unit/src/sqlbuild/cli/commands/_helpers/runtime/helpers.py b/tests/unit/src/sqlbuild/cli/commands/_helpers/runtime/helpers.py index a707305d5c..6e4fc81e4e 100644 --- a/tests/unit/src/sqlbuild/cli/commands/_helpers/runtime/helpers.py +++ b/tests/unit/src/sqlbuild/cli/commands/_helpers/runtime/helpers.py @@ -2,6 +2,7 @@ from __future__ import annotations +import json from pathlib import Path from sqlbuild._native import ( @@ -48,3 +49,13 @@ def write_case_adapter_file(*, project_dir: Path, contents: str) -> None: (project_dir / "adapters").mkdir(exist_ok=True) (project_dir / "adapters" / "custom.py").write_text(contents, encoding="utf-8") + + +def write_dbt_profile(profiles_dir: Path, *, profile: str, output: dict[str, object]) -> None: + """Write a `profiles.yml` whose `profile` has one `dev` output with `output`'s fields.""" + + profiles_dir.mkdir(parents=True, exist_ok=True) + fields: str = "".join(f" {name}: {json.dumps(value)}\n" for name, value in output.items()) + (profiles_dir / "profiles.yml").write_text( + f"{profile}:\n target: dev\n outputs:\n dev:\n{fields}", encoding="utf-8" + ) diff --git a/tests/unit/src/sqlbuild/cli/commands/_helpers/runtime/test_connection.py b/tests/unit/src/sqlbuild/cli/commands/_helpers/runtime/test_connection.py index dbd92dbfe6..fd1642cba4 100644 --- a/tests/unit/src/sqlbuild/cli/commands/_helpers/runtime/test_connection.py +++ b/tests/unit/src/sqlbuild/cli/commands/_helpers/runtime/test_connection.py @@ -4,12 +4,10 @@ import pytest -from sqlbuild import _native as native from sqlbuild._native import ( DiscoveredProjectInputs, LocalConfig, LocalTargetConfig, - NormalizedDbtProfileConnection, ProjectConfig, ProjectConfigError, TargetConfig, @@ -29,6 +27,7 @@ ResolveEnvironmentConnectionConfigTestCase, ResolveProjectConnectionConfigTestCase, ) +from tests.unit.src.sqlbuild.cli.commands._helpers.runtime.helpers import write_dbt_profile @pytest.mark.parametrize( @@ -255,26 +254,23 @@ def test_given_named_dbt_profile_connection_when_resolving_then_routes_downstrea name="demo", adapter="snowflake", default_target="dev", - connections={"dbt": {"source": "dbt_profile", "profile": "analytics", "target": "dev"}}, + connections={ + "dbt": { + "source": "dbt_profile", + "profile": "analytics", + "target": "dev", + "profiles_dir": "profiles", + } + }, targets={"dev": TargetConfig(connection_name="dbt")}, ), local_config=LocalConfig(), ) - def resolve_raw_dbt_profile_connection(**kwargs: object) -> NormalizedDbtProfileConnection: - assert kwargs["raw_config"] == { - "source": "dbt_profile", - "profile": "analytics", - "target": "dev", - } - return NormalizedDbtProfileConnection( - adapter="snowflake", connection={"account": "acct", "database": "ANALYTICS"} - ) - - monkeypatch.setattr( - native, - "resolve_raw_dbt_profile_connection", - resolve_raw_dbt_profile_connection, + write_dbt_profile( + tmp_path / "profiles", + profile="analytics", + output={"type": "snowflake", "account": "acct", "database": "ANALYTICS"}, ) connection: dict[str, object] = resolve_project_connection_config( @@ -293,7 +289,7 @@ def resolve_raw_dbt_profile_connection(**kwargs: object) -> NormalizedDbtProfile "source": "dbt_profile", "profile": "analytics", "target": "dev", - "profiles_dir": "../profiles", + "profiles_dir": "profiles", "account": "override-acct", "client_request_mfa_token": True, }, @@ -324,16 +320,10 @@ def test_given_dbt_profile_connection_when_resolving_then_merges_user_overrides( local_config=LocalConfig(), ) - def resolve_raw_dbt_profile_connection(**kwargs: object) -> NormalizedDbtProfileConnection: - return NormalizedDbtProfileConnection( - adapter=BuiltinAdapter.SNOWFLAKE.value, - connection=test_case.profile_connection, - ) - - monkeypatch.setattr( - native, - "resolve_raw_dbt_profile_connection", - resolve_raw_dbt_profile_connection, + write_dbt_profile( + tmp_path / "profiles", + profile="analytics", + output={"type": "snowflake", **test_case.profile_connection}, ) connection: dict[str, object] = resolve_connection_config( diff --git a/tests/unit/src/sqlbuild/cli/commands/main/dbt/_test_types.py b/tests/unit/src/sqlbuild/cli/commands/main/dbt/_test_types.py index 6a939e8c9d..14cfbfc306 100644 --- a/tests/unit/src/sqlbuild/cli/commands/main/dbt/_test_types.py +++ b/tests/unit/src/sqlbuild/cli/commands/main/dbt/_test_types.py @@ -58,7 +58,7 @@ class DbtInitOutputTestCase: class DbtInitProjectDirDefaultTestCase: description: str dbt_project_text: str - expected_dbt_project_dir: str + expected_config_line: str @dataclass(frozen=True) diff --git a/tests/unit/src/sqlbuild/cli/commands/main/dbt/helpers.py b/tests/unit/src/sqlbuild/cli/commands/main/dbt/helpers.py index 41f49a2dd9..a2c6148d42 100644 --- a/tests/unit/src/sqlbuild/cli/commands/main/dbt/helpers.py +++ b/tests/unit/src/sqlbuild/cli/commands/main/dbt/helpers.py @@ -1,5 +1,7 @@ from __future__ import annotations +import json +import sys from pathlib import Path from sqlbuild._native import ( @@ -10,6 +12,8 @@ ) from tests.unit.src.sqlbuild.cli.commands.main.dbt._test_types import DbtAutoInitTestCase +MINIMAL_SQLBUILD_PROJECT: str = 'name = "demo"\nadapter = "duckdb"\n' + def build_empty_dbt_plan() -> DbtInteropPlan: """Build a minimal dbt interop plan for CLI output tests.""" @@ -42,6 +46,34 @@ def prepare_dbt_auto_init_dirs(*, test_case: DbtAutoInitTestCase, tmp_path: Path def write_minimal_sqlbuild_project(project_dir: Path) -> None: """Write a minimal SQLBuild project config for wrapper tests.""" + (project_dir / "sqlbuild_project.toml").write_text(MINIMAL_SQLBUILD_PROJECT, encoding="utf-8") + + +def write_fake_dbt_executable(directory: Path, *, exit_code: int) -> Path: + """Write an executable stand-in for dbt that records its arguments and exits.""" + + script: Path = directory / "fake_dbt" + script.write_text( + f"#!{sys.executable}\n" + "import json, pathlib, sys\n" + f"pathlib.Path({str(directory / 'dbt_args.json')!r}).write_text(json.dumps(sys.argv[1:]))\n" + f"sys.exit({exit_code!r})\n", + encoding="utf-8", + ) + script.chmod(0o755) + return script + + +def read_fake_dbt_args(directory: Path) -> tuple[str, ...]: + """The arguments the stand-in dbt in `directory` last received.""" + + return tuple(json.loads((directory / "dbt_args.json").read_text(encoding="utf-8"))) + + +def write_duckdb_sqlbuild_project(project_dir: Path) -> None: + """Write a SQLBuild project config with an inline DuckDB connection.""" + (project_dir / "sqlbuild_project.toml").write_text( - 'name = "demo"\nadapter = "duckdb"\n', encoding="utf-8" + 'name = "demo"\nadapter = "duckdb"\n\n[connection]\npath = "warehouse.duckdb"\n', + encoding="utf-8", ) diff --git a/tests/unit/src/sqlbuild/cli/commands/main/dbt/test_dbt.py b/tests/unit/src/sqlbuild/cli/commands/main/dbt/test_dbt.py index 5cbfd27540..f3acc3ef7a 100644 --- a/tests/unit/src/sqlbuild/cli/commands/main/dbt/test_dbt.py +++ b/tests/unit/src/sqlbuild/cli/commands/main/dbt/test_dbt.py @@ -8,8 +8,6 @@ from _pytest.capture import CaptureResult from sqlbuild._native import ( - DbtInitRequest, - DbtInitResult, DbtInteropCommand, DbtInteropExecutionRequest, DbtInteropPlan, @@ -22,10 +20,15 @@ DbtPlanProgressTestCase, ) from tests.unit.src.sqlbuild.cli.commands.main.dbt.helpers import ( + MINIMAL_SQLBUILD_PROJECT, build_empty_dbt_plan, prepare_dbt_auto_init_dirs, + read_fake_dbt_args, + write_duckdb_sqlbuild_project, + write_fake_dbt_executable, write_minimal_sqlbuild_project, ) +from tests.unit.src.sqlbuild.integrations.dbt.profile.helpers import write_duckdb_dbt_workspace @pytest.mark.parametrize( @@ -148,7 +151,7 @@ def execute_dbt_interop_from_project(request: DbtInteropExecutionRequest) -> int DbtDebugWrapperTestCase( description="debug runs dbt then SQLBuild diagnostics and skips SQLBuild connection", args=("--project-dir", "dbt_project", "--no-connection"), - expected_dbt_args=("--project-dir", "dbt_project", "--no-connection"), + expected_dbt_args=("debug", "--project-dir"), expected_sqlbuild_no_connection=True, expected_exit_code=0, dbt_exit_code=0, @@ -163,7 +166,7 @@ def execute_dbt_interop_from_project(request: DbtInteropExecutionRequest) -> int "--project-dir", "dbt_project", ), - expected_dbt_args=("--project-dir", "dbt_project"), + expected_dbt_args=("debug", "--project-dir"), expected_sqlbuild_no_connection=False, expected_exit_code=1, dbt_exit_code=1, @@ -177,34 +180,12 @@ def test_given_dbt_debug_command_when_running_then_invokes_dbt_and_sqlbuild_debu monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str], ) -> None: - dbt_calls: list[tuple[Path, tuple[str, ...], object, object]] = [] - sqlbuild_calls: list[tuple[Path | None, bool, bool, bool]] = [] - - def debug_dbt_from_project( - *, - project_dir: Path, - args: tuple[str, ...], - stdout_stream: object, - stderr_stream: object, - ) -> int: - dbt_calls.append((project_dir, args, stdout_stream, stderr_stream)) - return test_case.dbt_exit_code - - def run_sqlbuild_debug( - project_dir: Path | None, - no_color: bool, - no_connection: bool, - json_output: bool, - ) -> int: - sqlbuild_calls.append((project_dir, no_color, no_connection, json_output)) - return 0 - - monkeypatch.setattr( - "sqlbuild._native.debug_dbt_from_project", - debug_dbt_from_project, + (tmp_path / "dbt_project").mkdir() + monkeypatch.setenv( + "DBT_EXECUTABLE", + str(write_fake_dbt_executable(tmp_path, exit_code=test_case.dbt_exit_code)), ) - monkeypatch.setattr("sqlbuild._native.run_debug_command", run_sqlbuild_debug) - write_minimal_sqlbuild_project(tmp_path) + write_duckdb_sqlbuild_project(tmp_path) exit_code: int = run_dbt_command( command=DbtInteropCommand.DEBUG, @@ -214,11 +195,16 @@ def run_sqlbuild_debug( ) assert exit_code == test_case.expected_exit_code - assert dbt_calls == [(tmp_path, test_case.expected_dbt_args, sys.stdout, sys.stderr)] - assert sqlbuild_calls == [(tmp_path, True, test_case.expected_sqlbuild_no_connection, False)] + assert read_fake_dbt_args(tmp_path) == ( + *test_case.expected_dbt_args, + str(tmp_path / "dbt_project"), + ) captured: CaptureResult[str] = capsys.readouterr() for fragment in test_case.expected_stderr_fragments: assert fragment in captured.err + assert ("skipped by --no-connection" in captured.out) is ( + test_case.expected_sqlbuild_no_connection + ) @pytest.mark.parametrize( @@ -256,25 +242,6 @@ def test_given_existing_sqlbuild_project_when_running_dbt_command_then_uses_expe ) -> None: dbt_project_dir: Path = prepare_dbt_auto_init_dirs(test_case=test_case, tmp_path=tmp_path) forwarded_project_dirs: list[Path] = [] - init_requests: list[DbtInitRequest] = [] - - def run_dbt_profile_init(*, request: DbtInitRequest) -> DbtInitResult: - init_requests.append(request) - output_dir: Path | None = request.sqb_output_dir - assert output_dir is not None - output_dir.mkdir(parents=True, exist_ok=True) - (output_dir / "sqlbuild_project.toml").write_text( - 'name = "demo"\nadapter = "duckdb"\n', encoding="utf-8" - ) - return DbtInitResult( - output_dir=output_dir, - project_file=output_dir / "sqlbuild_project.toml", - project_name="demo", - adapter="duckdb", - target_name=request.target_name or "dev", - profile_name="demo", - toml='name = "demo"\n', - ) def plan_dbt_interop_from_project( *, @@ -288,10 +255,6 @@ def plan_dbt_interop_from_project( forwarded_project_dirs.append(project_dir) return build_empty_dbt_plan() - monkeypatch.setattr( - "sqlbuild._native.build_dbt_init_project", - run_dbt_profile_init, - ) monkeypatch.setattr( "sqlbuild._native.plan_dbt_interop_from_project", plan_dbt_interop_from_project, @@ -306,7 +269,9 @@ def plan_dbt_interop_from_project( assert exit_code == 0 assert forwarded_project_dirs[0].name == test_case.expected_forwarded_project_dir_name - assert init_requests == [] + assert (forwarded_project_dirs[0] / "sqlbuild_project.toml").read_text(encoding="utf-8") == ( + MINIMAL_SQLBUILD_PROJECT + ) @pytest.mark.parametrize( @@ -342,27 +307,9 @@ def test_given_missing_sqlbuild_twin_when_running_dbt_command_then_initializes_a capsys: pytest.CaptureFixture[str], ) -> None: dbt_project_dir: Path = prepare_dbt_auto_init_dirs(test_case=test_case, tmp_path=tmp_path) + write_duckdb_dbt_workspace(dbt_project_dir) forwarded_project_dirs: list[Path] = [] forwarded_args: list[tuple[str, ...]] = [] - init_requests: list[DbtInitRequest] = [] - - def run_dbt_profile_init(*, request: DbtInitRequest) -> DbtInitResult: - init_requests.append(request) - assert request.progress_callbacks.start is not None - request.progress_callbacks.start("Inspecting dbt project and profile...") - output_dir: Path | None = request.sqb_output_dir - assert output_dir is not None - output_dir.mkdir(parents=True, exist_ok=True) - write_minimal_sqlbuild_project(output_dir) - return DbtInitResult( - output_dir=output_dir, - project_file=output_dir / "sqlbuild_project.toml", - project_name="demo", - adapter="duckdb", - target_name=request.target_name or "dev", - profile_name="demo", - toml='name = "demo"\n', - ) def plan_dbt_interop_from_project( *, @@ -377,10 +324,6 @@ def plan_dbt_interop_from_project( forwarded_args.append(args) return build_empty_dbt_plan() - monkeypatch.setattr( - "sqlbuild._native.build_dbt_init_project", - run_dbt_profile_init, - ) monkeypatch.setattr( "sqlbuild._native.plan_dbt_interop_from_project", plan_dbt_interop_from_project, @@ -402,13 +345,8 @@ def plan_dbt_interop_from_project( assert forwarded_project_dirs[0].name == test_case.expected_forwarded_project_dir_name assert Path(forwarded_args[0][forwarded_args[0].index("--project-dir") + 1]).is_absolute() assert Path(forwarded_args[0][forwarded_args[0].index("--profiles-dir") + 1]).is_absolute() - assert bool(init_requests) is test_case.expected_init_called - request: DbtInitRequest = init_requests[0] - assert request.dbt_project_dir.name == test_case.expected_request_dbt_project_dir_name - assert getattr(request.profiles_dir, "as_posix", lambda: None)() == ( - test_case.expected_request_profiles_dir + twin_config: str = (forwarded_project_dirs[0] / "sqlbuild_project.toml").read_text( + encoding="utf-8" ) - assert request.target_name == test_case.expected_request_target_name - assert request.sqb_output_dir is not None - assert request.sqb_output_dir.name == "sqlbuild_project" - assert request.skip_dbt_debug is True + assert f'project_dir = "../{test_case.expected_request_dbt_project_dir_name}' in twin_config + assert f'target = "{test_case.expected_request_target_name}"' in twin_config diff --git a/tests/unit/src/sqlbuild/cli/commands/main/dbt/test_init.py b/tests/unit/src/sqlbuild/cli/commands/main/dbt/test_init.py index b232b21abc..1238619bcc 100644 --- a/tests/unit/src/sqlbuild/cli/commands/main/dbt/test_init.py +++ b/tests/unit/src/sqlbuild/cli/commands/main/dbt/test_init.py @@ -6,14 +6,14 @@ from _pytest.capture import CaptureResult import sqlbuild._native as dbt_init_module -import sqlbuild._native as dbt_profile_init -from sqlbuild._native import DbtInitCommandRequest, DbtInitRequest, DbtInitResult +from sqlbuild._native import DbtInitCommandRequest from sqlbuild.integrations.dbt.exceptions import DbtProfileError from tests.unit.src.sqlbuild.cli.commands.main.dbt._test_types import ( DbtInitOutputTestCase, DbtInitProjectDirDefaultTestCase, DbtInitValidationOrderTestCase, ) +from tests.unit.src.sqlbuild.integrations.dbt.profile.helpers import write_duckdb_dbt_workspace from tests.unit.src.sqlbuild.presentation.helpers import use_color_terminal @@ -29,7 +29,7 @@ "SQLBuild project created", "Setup summary:", "Config file", - "/workspace/sqlbuild_project/sqlbuild_project.toml", + "sqlbuild_project/sqlbuild_project.toml", "What SQLBuild created:", "SQLBuild twin config", "This points SQLBuild at your dbt project and profile.", @@ -43,34 +43,15 @@ ) def test_given_dbt_init_when_running_then_outputs_progress_and_dbt_first_next_steps( test_case: DbtInitOutputTestCase, + tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str], ) -> None: - def run_dbt_profile_init(*, request: DbtInitRequest) -> DbtInitResult: - assert request.progress_callbacks.start is not None - assert request.progress_callbacks.complete is not None - request.progress_callbacks.start("Inspecting dbt project and profile...") - request.progress_callbacks.complete("Inspected dbt project and profile.") - return DbtInitResult( - output_dir=Path("/workspace/sqlbuild_project"), - project_file=Path("/workspace/sqlbuild_project/sqlbuild_project.toml"), - project_name="analytics", - adapter="duckdb", - target_name="dev", - profile_name="analytics_profile", - toml='name = "analytics"\n', - ) - - monkeypatch.setattr(dbt_profile_init, "build_dbt_init_project", run_dbt_profile_init) - monkeypatch.setattr( - dbt_profile_init, - "validate_dbt_init_project", - lambda *, request: None, - ) + write_duckdb_dbt_workspace(tmp_path) exit_code: int = dbt_init_module.run_dbt_init_command( DbtInitCommandRequest( - cwd=Path("/workspace"), + cwd=tmp_path, dbt_project_dir="dbt_project", profiles_dir="profiles", profile_name=None, @@ -110,41 +91,15 @@ def run_dbt_profile_init(*, request: DbtInitRequest) -> DbtInitResult: ) def test_given_dbt_init_dry_run_when_running_then_outputs_preview_document( test_case: DbtInitOutputTestCase, + tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str], ) -> None: - def run_dbt_profile_init(*, request: DbtInitRequest) -> DbtInitResult: - assert request.progress_callbacks.start is not None - assert request.progress_callbacks.complete is not None - request.progress_callbacks.start("Rendering dbt profile connection...") - request.progress_callbacks.complete("Rendered dbt profile connection.") - return DbtInitResult( - output_dir=Path("/workspace/sqlbuild_project"), - project_file=Path("/workspace/sqlbuild_project/sqlbuild_project.toml"), - project_name="analytics", - adapter="duckdb", - target_name="dev", - profile_name="analytics_profile", - toml=( - 'name = "analytics"\n' - 'adapter = "duckdb"\n' - "\n" - "[targets.dev.connection]\n" - 'source = "dbt_profile"\n' - ), - dry_run=True, - ) - - monkeypatch.setattr(dbt_profile_init, "build_dbt_init_project", run_dbt_profile_init) - monkeypatch.setattr( - dbt_profile_init, - "validate_dbt_init_project", - lambda *, request: None, - ) + write_duckdb_dbt_workspace(tmp_path) exit_code: int = dbt_init_module.run_dbt_init_command( DbtInitCommandRequest( - cwd=Path("/workspace"), + cwd=tmp_path, dbt_project_dir="dbt_project", profiles_dir="profiles", profile_name=None, @@ -176,7 +131,7 @@ def run_dbt_profile_init(*, request: DbtInitRequest) -> DbtInitResult: "\033[34m\033[1mSQLBuild project created\033[0m", "\033[1mSetup summary\033[0m:", "\033[34mProject", - "analytics", + "shop", "\033[1mWhat SQLBuild created\033[0m:", "\033[34mSQLBuild twin config\033[0m", "\033[1mNext steps\033[0m:", @@ -190,35 +145,16 @@ def run_dbt_profile_init(*, request: DbtInitRequest) -> DbtInitResult: ) def test_given_color_terminal_when_running_dbt_init_then_it_styles_output( test_case: DbtInitOutputTestCase, + tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str], ) -> None: - def run_dbt_profile_init(*, request: DbtInitRequest) -> DbtInitResult: - assert request.progress_callbacks.start is not None - assert request.progress_callbacks.complete is not None - request.progress_callbacks.start("Inspecting dbt project and profile...") - request.progress_callbacks.complete("Inspected dbt project and profile.") - return DbtInitResult( - output_dir=Path("/workspace/sqlbuild_project"), - project_file=Path("/workspace/sqlbuild_project/sqlbuild_project.toml"), - project_name="analytics", - adapter="duckdb", - target_name="dev", - profile_name="analytics_profile", - toml='name = "analytics"\n', - ) - + write_duckdb_dbt_workspace(tmp_path) use_color_terminal(monkeypatch) - monkeypatch.setattr(dbt_profile_init, "build_dbt_init_project", run_dbt_profile_init) - monkeypatch.setattr( - dbt_profile_init, - "validate_dbt_init_project", - lambda *, request: None, - ) exit_code: int = dbt_init_module.run_dbt_init_command( DbtInitCommandRequest( - cwd=Path("/workspace"), + cwd=tmp_path, dbt_project_dir="dbt_project", profiles_dir="profiles", profile_name=None, @@ -242,7 +178,7 @@ def run_dbt_profile_init(*, request: DbtInitRequest) -> DbtInitResult: DbtInitProjectDirDefaultTestCase( description="defaults project dir to cwd when dbt project file exists", dbt_project_text="name: analytics\nprofile: analytics\n", - expected_dbt_project_dir=".", + expected_config_line='project_dir = "../{cwd}"', ) ], ids=lambda case: case.description, @@ -251,31 +187,15 @@ def test_given_dbt_project_in_cwd_when_running_dbt_init_then_project_dir_default test_case: DbtInitProjectDirDefaultTestCase, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, + capsys: pytest.CaptureFixture[str], ) -> None: (tmp_path / "dbt_project.yml").write_text(test_case.dbt_project_text, encoding="utf-8") - captured_request: dict[str, DbtInitRequest] = {} - - def validate_dbt_profile_init_request(*, request: DbtInitRequest) -> None: - assert request.dbt_project_dir == Path(test_case.expected_dbt_project_dir) - - def run_dbt_profile_init(*, request: DbtInitRequest) -> DbtInitResult: - captured_request["request"] = request - return DbtInitResult( - output_dir=tmp_path / "sqlbuild_project", - project_file=tmp_path / "sqlbuild_project" / "sqlbuild_project.toml", - project_name="analytics", - adapter="duckdb", - target_name="dev", - profile_name="analytics", - toml='name = "analytics"\n', - ) - - monkeypatch.setattr( - dbt_profile_init, - "validate_dbt_init_project", - validate_dbt_profile_init_request, + (tmp_path / "profiles.yml").write_text( + "analytics:\n target: dev\n outputs:\n dev:\n type: duckdb\n" + " path: warehouse.duckdb\n schema: main\n", + encoding="utf-8", ) - monkeypatch.setattr(dbt_profile_init, "build_dbt_init_project", run_dbt_profile_init) + monkeypatch.delenv("DBT_PROFILES_DIR", raising=False) exit_code: int = dbt_init_module.run_dbt_init_command( DbtInitCommandRequest( @@ -292,7 +212,7 @@ def run_dbt_profile_init(*, request: DbtInitRequest) -> DbtInitResult: ) assert exit_code == 0 - assert captured_request["request"].dbt_project_dir == Path(test_case.expected_dbt_project_dir) + assert test_case.expected_config_line.format(cwd=tmp_path.name) in capsys.readouterr().out @pytest.mark.parametrize( @@ -310,18 +230,10 @@ def test_given_invalid_dbt_init_inputs_when_running_then_validation_happens_befo tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ) -> None: - def validate_dbt_profile_init_request(*, request: DbtInitRequest) -> None: - raise DbtProfileError(f"dbt config file not found: {test_case.expected_error_fragment}") - - def run_dbt_profile_init(*, request: DbtInitRequest) -> DbtInitResult: - raise AssertionError("init should not run after validation failure") - - monkeypatch.setattr( - dbt_profile_init, - "validate_dbt_init_project", - validate_dbt_profile_init_request, + (tmp_path / "dbt_project.yml").write_text( + "name: analytics\nprofile: analytics\n", encoding="utf-8" ) - monkeypatch.setattr(dbt_profile_init, "build_dbt_init_project", run_dbt_profile_init) + monkeypatch.setenv("DBT_PROFILES_DIR", str(tmp_path / "profiles")) with pytest.raises(DbtProfileError, match=test_case.expected_error_fragment): dbt_init_module.run_dbt_init_command( @@ -331,9 +243,11 @@ def run_dbt_profile_init(*, request: DbtInitRequest) -> DbtInitResult: profiles_dir=None, profile_name=None, target_name=None, - sqb_output_dir=None, - dry_run=True, + sqb_output_dir="sqlbuild_project", + dry_run=False, overwrite=False, skip_dbt_debug=True, ) ) + + assert not (tmp_path / "sqlbuild_project").exists() diff --git a/tests/unit/src/sqlbuild/compiler/planner/_helpers/test_fixture_planning_work.py b/tests/unit/src/sqlbuild/compiler/planner/_helpers/test_fixture_planning_work.py index 7f41f096e5..752f165308 100644 --- a/tests/unit/src/sqlbuild/compiler/planner/_helpers/test_fixture_planning_work.py +++ b/tests/unit/src/sqlbuild/compiler/planner/_helpers/test_fixture_planning_work.py @@ -1,11 +1,9 @@ from __future__ import annotations from dataclasses import replace -from unittest.mock import Mock import pytest -import sqlbuild._native as sql_test_assembly from sqlbuild import _native as fixture_completion from sqlbuild._native import ( CompiledObjectKey, @@ -21,6 +19,7 @@ SqlTestSelection, build_selected_test_entries, compiled_model_record, + fixture_inference_count, project_with, ) from sqlbuild.compiler.compile.types import ( @@ -186,7 +185,6 @@ def test_given_many_tests_when_building_entries_then_project_fixture_metadata_is ) def test_given_complete_authoritative_fixture_when_completing_then_skips_column_analysis( test_case: CompleteFixturePlanningTestCase, - monkeypatch: pytest.MonkeyPatch, ) -> None: _, project = build_test_and_project( PlanTestChainTestCase( @@ -218,12 +216,7 @@ def test_given_complete_authoritative_fixture_when_completing_then_skips_column_ }, ) fixture_completion.fixture_read_cache_info(reset=True) - fixture_inference: Mock = Mock(wraps=sql_test_assembly.infer_fixture_column_facts) - monkeypatch.setattr( - sql_test_assembly, - "infer_fixture_column_facts", - fixture_inference, - ) + fixture_inference_count(reset=True) completed: RelationFixtureCompletion = fixture_completion.build_relation_fixture_completion( project=project_with(project=project, models=(model,)), @@ -238,7 +231,7 @@ def test_given_complete_authoritative_fixture_when_completing_then_skips_column_ assert completed.diagnostics == () assert completed.fixture_sql_by_key[source_key] == test_case.expected_fixture_sql assert fixture_completion.fixture_read_cache_info()[0] == test_case.expected_analysis_calls - assert fixture_inference.call_count == test_case.expected_inference_calls + assert fixture_inference_count() == test_case.expected_inference_calls if __name__ == "__main__": diff --git a/tests/unit/src/sqlbuild/compiler/planner/classes/helpers.py b/tests/unit/src/sqlbuild/compiler/planner/classes/helpers.py index a504096ade..066d2796fc 100644 --- a/tests/unit/src/sqlbuild/compiler/planner/classes/helpers.py +++ b/tests/unit/src/sqlbuild/compiler/planner/classes/helpers.py @@ -10,11 +10,7 @@ import pytest -import sqlbuild._native as native_module -from sqlbuild._native import ( - FixtureColumnInference, - infer_fixture_column_facts, -) +from sqlbuild._native import fixture_inference_count from sqlbuild.adapter.contract.models import ExpressionInferenceProfile ORDERS_SQL: str = 'SELECT o.order_id, o.amount_cents FROM __ref("stg_orders") AS o' @@ -91,19 +87,10 @@ def upgrade_sqlbuild(root: Path, monkeypatch: pytest.MonkeyPatch) -> None: } -def count_fixture_inferences(monkeypatch: pytest.MonkeyPatch) -> list[int]: - """Count every fixture inference computed instead of reused.""" - - inferences: list[int] = [] - - def counting( - *, query_sql: str, inference_profile: ExpressionInferenceProfile - ) -> FixtureColumnInference | None: - inferences.append(1) - return infer_fixture_column_facts(query_sql=query_sql, inference_profile=inference_profile) +def reset_fixture_inference_count() -> None: + """Start counting fixture inferences computed instead of reused from zero.""" - monkeypatch.setattr(native_module, "infer_fixture_column_facts", counting) - return inferences + fixture_inference_count(reset=True) class SqlTestsProject: diff --git a/tests/unit/src/sqlbuild/compiler/planner/classes/test_fixture_column_inferences.py b/tests/unit/src/sqlbuild/compiler/planner/classes/test_fixture_column_inferences.py index 8fa5c94b32..ebe6d8d2d6 100644 --- a/tests/unit/src/sqlbuild/compiler/planner/classes/test_fixture_column_inferences.py +++ b/tests/unit/src/sqlbuild/compiler/planner/classes/test_fixture_column_inferences.py @@ -7,6 +7,7 @@ from sqlbuild._native import ( FixtureColumnInference, FixtureColumnInferences, + fixture_inference_count, infer_fixture_column_facts, ) from tests.unit.src.sqlbuild.compiler.planner.classes._test_types import ( @@ -15,7 +16,7 @@ from tests.unit.src.sqlbuild.compiler.planner.classes.helpers import ( FIXTURE_PROFILES, FIXTURE_QUERIES, - count_fixture_inferences, + reset_fixture_inference_count, ) @@ -41,15 +42,16 @@ ids=lambda case: case.description, ) def test_given_fixture_queries_when_inferring_then_matches_direct_inference_with_minimal_work( - test_case: FixtureColumnInferencesTestCase, monkeypatch: pytest.MonkeyPatch + test_case: FixtureColumnInferencesTestCase, ) -> None: - inferences: list[int] = count_fixture_inferences(monkeypatch) + reset_fixture_inference_count() memo: FixtureColumnInferences = FixtureColumnInferences() results: list[FixtureColumnInference | None] = [ memo.infer(query_sql=FIXTURE_QUERIES[query], inference_profile=FIXTURE_PROFILES[profile]) for query, profile in test_case.requests ] + inferences: int = fixture_inference_count() assert results == [ infer_fixture_column_facts( @@ -58,7 +60,7 @@ def test_given_fixture_queries_when_inferring_then_matches_direct_inference_with for query, profile in test_case.requests ] assert all(result is not None for result in results) - assert len(inferences) == test_case.expected_inferences + assert inferences == test_case.expected_inferences if __name__ == "__main__": diff --git a/tests/unit/src/sqlbuild/integrations/dbt/helpers.py b/tests/unit/src/sqlbuild/integrations/dbt/helpers.py index 984791a758..5caa2f16bb 100644 --- a/tests/unit/src/sqlbuild/integrations/dbt/helpers.py +++ b/tests/unit/src/sqlbuild/integrations/dbt/helpers.py @@ -23,6 +23,7 @@ DbtCommandResult, DbtEventStream, DbtEventStreamUpdate, + DbtNodeExecutionResult, DbtRunner, DiscoveredProjectInputs, DiscoveredSqlModelFile, @@ -499,8 +500,9 @@ def fake_dbt_argv( lines: tuple[str, ...], sleep_seconds: float = 0.0, after_lines: tuple[str, ...] = (), + returncode: int = 0, ) -> tuple[str, ...]: - """argv of a stand-in dbt process printing `lines`, pausing, then printing `after_lines`.""" + """argv of a stand-in dbt process printing `lines`, pausing, printing `after_lines`, exiting.""" script: Path = directory / "fake_dbt.py" _ = script.write_text( @@ -508,7 +510,33 @@ def fake_dbt_argv( f"sys.stdout.write({''.join(lines)!r})\n" "sys.stdout.flush()\n" f"time.sleep({sleep_seconds!r})\n" - f"sys.stdout.write({''.join(after_lines)!r})\n", + f"sys.stdout.write({''.join(after_lines)!r})\n" + f"sys.exit({returncode!r})\n", encoding="utf-8", ) return (sys.executable, str(script)) + + +def dbt_result_line(result: DbtNodeExecutionResult) -> str: + """The dbt JSON log line that streams `result`.""" + + return ( + json.dumps( + { + "data": { + "execution_time": result.execution_time, + "index": result.index, + "total": result.total, + "status": result.status, + "node_info": { + "node_name": result.node_name, + "resource_type": result.resource_type, + "node_status": result.status, + "unique_id": result.unique_id, + }, + }, + "info": {"level": "info", "name": "NodeFinished", "msg": result.status}, + } + ) + + "\n" + ) diff --git a/tests/unit/src/sqlbuild/integrations/dbt/test_execute.py b/tests/unit/src/sqlbuild/integrations/dbt/test_execute.py index da5187c81c..4893e6c29e 100644 --- a/tests/unit/src/sqlbuild/integrations/dbt/test_execute.py +++ b/tests/unit/src/sqlbuild/integrations/dbt/test_execute.py @@ -8,7 +8,6 @@ import pytest -import sqlbuild._native as event_module from sqlbuild import _native as execute_module from sqlbuild import _native as phases_module from sqlbuild import _native as plan_module @@ -45,6 +44,8 @@ from tests.unit.src.sqlbuild.integrations.dbt.helpers import ( CompileOnlyDbtRunner, build_orders_dbt_graph, + dbt_result_line, + fake_dbt_argv, ) @@ -226,15 +227,17 @@ def test_given_ordinary_execution_when_compiling_then_never_uses_dbt_state( ) def test_given_ordinary_dbt_events_when_executing_then_no_fingerprint_or_watermark_callback_is_used( test_case: DbtExecutionSummaryFooterTestCase, - monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, ) -> None: - observed: dict[str, object] = {} - - def execute_commands(**kwargs: object) -> DbtCommandExecutionResult: - observed.update(kwargs) - return DbtCommandExecutionResult(returncode=0) - - monkeypatch.setattr(phases_module, "execute_dbt_commands", execute_commands) + streamed: DbtNodeExecutionResult = DbtNodeExecutionResult( + unique_id="model.analytics.orders", + resource_type="model", + node_name="orders", + status="success", + index=1, + total=1, + execution_time=0.1, + ) command: DbtInteropCommand = DbtInteropCommand.RUN invocation: DbtInteropInvocation = DbtInteropInvocation( dbt_executable="dbt", @@ -243,19 +246,20 @@ def execute_commands(**kwargs: object) -> DbtCommandExecutionResult: routed=DbtInteropRoutedArgs(command=command), discovered_inputs=cast(DiscoveredProjectInputs, SimpleNamespace()), effective_sqlbuild_args=(), - dbt_options=DbtCliOptions(project_dir=Path("/dbt")), + dbt_options=DbtCliOptions(project_dir=tmp_path), dbt_vars={}, - runner=cast(object, SimpleNamespace()), + runner=SimpleNamespace(invoke=lambda **_: SimpleNamespace(returncode=1, stdout="")), ) result: DbtCommandExecutionResult = phases_module.execute_dbt_without_state_tracking( - request=DbtInteropExecutionRequest(command=command, project_dir=Path("/project"), args=()), + request=DbtInteropExecutionRequest(command=command, project_dir=tmp_path, args=()), invocation=invocation, - merged_dbt_argv=("dbt", "run"), + merged_dbt_argv=fake_dbt_argv(tmp_path, lines=(dbt_result_line(streamed),)), ) assert result.returncode == 0 - assert "on_node_result" not in observed + assert [node.unique_id for node in result.node_results] == [streamed.unique_id] + assert not (tmp_path / ".sqlbuild").exists() assert test_case.expected_footer == "no-state-callback" @@ -406,7 +410,7 @@ def test_given_dbt_native_state_when_building_execution_argv_then_preserves_nati ) def test_given_planned_dbt_tests_when_executing_then_does_not_resolve_selection_again( test_case: DbtExecutionSelectionStatusTestCase, - monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, ) -> None: class NoSelectionRunner: def invoke(self, **kwargs: object) -> object: @@ -437,17 +441,18 @@ def invoke(self, **kwargs: object) -> object: execution_time=0.1, ), ) - monkeypatch.setattr( - event_module, - "execute_dbt_json_event_stream", - lambda **kwargs: (0, streamed_results), - ) + lines: tuple[str, ...] = tuple(dbt_result_line(result) for result in streamed_results) progress_stream: StringIO = StringIO() result: DbtCommandExecutionResult = execute_dbt_commands( runner=cast(DbtRunner, NoSelectionRunner()), - options=DbtCliOptions(project_dir=Path("/dbt")), - merged_argv=("dbt", "test", "--select", planned_node.unique_id), + options=DbtCliOptions(project_dir=tmp_path), + merged_argv=( + *fake_dbt_argv(tmp_path, lines=lines), + "test", + "--select", + planned_node.unique_id, + ), progress_stream=progress_stream, stdout_stream=StringIO(), use_color=False, @@ -479,7 +484,7 @@ def invoke(self, **kwargs: object) -> object: ids=lambda case: case.description, ) def test_given_missing_streamed_nodes_when_finalizing_then_backfills_selected_artifact_results( - test_case: DbtResultBackfillTestCase, tmp_path: Path, monkeypatch: pytest.MonkeyPatch + test_case: DbtResultBackfillTestCase, tmp_path: Path ) -> None: streamed: DbtNodeExecutionResult = DbtNodeExecutionResult( unique_id="model.analytics.orders", @@ -507,9 +512,6 @@ def test_given_missing_streamed_nodes_when_finalizing_then_backfills_selected_ar ), encoding="utf-8", ) - monkeypatch.setattr( - event_module, "execute_dbt_json_event_stream", lambda **kwargs: (1, (streamed,)) - ) output: StringIO = StringIO() callbacks: list[DbtNodeExecutionResult] = [] progress: list[str] = [] @@ -517,7 +519,7 @@ def test_given_missing_streamed_nodes_when_finalizing_then_backfills_selected_ar result: DbtCommandExecutionResult = execute_dbt_commands( runner=DbtRunner(), options=DbtCliOptions(target_path=tmp_path), - merged_argv=("dbt", "build"), + merged_argv=fake_dbt_argv(tmp_path, lines=(dbt_result_line(streamed),), returncode=1), progress_stream=StringIO(), stdout_stream=output, use_color=False, @@ -530,14 +532,15 @@ def test_given_missing_streamed_nodes_when_finalizing_then_backfills_selected_ar ) assert result.returncode == 1 - assert result.node_results[0] == streamed + assert result.node_results[0].unique_id == streamed.unique_id + assert result.node_results[0].status == streamed.status assert len(result.node_results) == 2 appended: DbtNodeExecutionResult = result.node_results[1] assert appended.unique_id == "model.analytics.customers" assert appended.status == "skipped" assert (appended.index, appended.total) == (2, 3) assert appended.messages[0].message == test_case.expected_message - assert callbacks == [appended] + assert callbacks == [result.node_results[0], appended] assert progress == ["Finalizing dbt run...", "Finalized dbt run."] assert "customers" in output.getvalue() assert "inventory" not in output.getvalue() diff --git a/tests/unit/src/sqlbuild/lint/test_adapter_lexical_syntax.py b/tests/unit/src/sqlbuild/lint/test_adapter_lexical_syntax.py index c723e451f2..5dc2af107a 100644 --- a/tests/unit/src/sqlbuild/lint/test_adapter_lexical_syntax.py +++ b/tests/unit/src/sqlbuild/lint/test_adapter_lexical_syntax.py @@ -4,12 +4,10 @@ from pathlib import Path from typing import ClassVar -from unittest.mock import Mock import pytest import sqlbuild._native as fixture_formatting_module -import sqlbuild._native as native_module from sqlbuild._native import DiscoveredProjectInputs, SqlLexicalSyntax, discover_project_inputs from sqlbuild.adapters.duckdb.classes.duckdb_adapter import DuckDbAdapter from tests.unit.src.sqlbuild.lint._test_types import PassedAdapterSyntaxTestCase @@ -50,26 +48,5 @@ def test_given_passed_syntax_when_formatting_fixture_nulls_then_scanner_uses_it( assert "# ) custom comment" in updated[test_file.resolve()] -@pytest.mark.parametrize( - "test_case", - [PassedAdapterSyntaxTestCase("expansion scope uses the renderer syntax", _CUSTOM_SYNTAX)], - ids=lambda case: case.description, -) -def test_given_value_renderer_when_building_expansion_context_then_scope_uses_its_syntax( - test_case: PassedAdapterSyntaxTestCase, - tmp_path: Path, - monkeypatch: pytest.MonkeyPatch, -) -> None: - _ = write_fixture_format_project(tmp_path=tmp_path, fixture_sql="SELECT 1 AS id, TRUE AS flag") - build_scope: Mock = Mock(wraps=native_module.build_declaration_scope) - monkeypatch.setattr(native_module, "build_declaration_scope", build_scope) - - _ = native_module.build_sql_expansion_context( - project_dir=tmp_path, value_renderer=_CustomSyntaxDuckDbAdapter() - ) - - assert build_scope.call_args.kwargs["sql_lexical_syntax"] is test_case.expected_syntax - - if __name__ == "__main__": pytest.main([__file__, "-vv"]) From f4b012aa206a61ed615619a9a079c6b496ac5948 Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Sat, 17 Oct 2026 03:49:23 +0100 Subject: [PATCH 24/28] refactor: call native classes, entry points and context variables directly in a-l bindings Construct native pyclasses (progress reporters, styles, event writers, runners, cost scopes, validators, loops) in Rust, call the remaining keyword, positional and value-held entry points directly, dispatch CLI command implementations through a typed table, and hold launch, progress and warehouse-scope context variables natively. The format-selection work test now reads native stage counters (format_lint_batches, format_lint_files, build_project_graph) instead of replacing entry points. --- .../_helpers/analysis/binding_locations.rs | 2 +- .../_helpers/analysis/explicit_references.rs | 8 +- .../analysis_session/outcome_objects.rs | 4 +- .../_helpers/audit_command/command.rs | 94 ++++---- .../_helpers/build_command/command.rs | 10 +- .../_helpers/build_command/completion.rs | 25 +-- .../_helpers/build_command/defer_clone.rs | 149 ++++++------- .../_helpers/build_command/execution.rs | 103 +++++---- .../_helpers/build_command/invocation.rs | 42 ++-- .../_helpers/build_command/policies.rs | 2 +- .../_helpers/build_progress/audit_progress.rs | 2 +- .../build_progress/build_callbacks.rs | 15 +- .../_helpers/build_progress/terminal.rs | 6 +- .../build_progress/transient_lines.rs | 2 +- .../build_progress/transient_status.rs | 2 +- .../bindings/_helpers/cache/native_store.rs | 4 +- .../_helpers/cache/sql_test_scan_store.rs | 2 +- .../bindings/_helpers/check/check_command.rs | 2 +- .../src/bindings/_helpers/cli/dispatch.rs | 117 +++++++++- .../src/bindings/_helpers/cli/entry.rs | 18 +- .../src/bindings/_helpers/cli/launch_state.rs | 58 +++-- .../bindings/_helpers/cli/observability.rs | 111 +++++----- .../src/bindings/_helpers/cli/progress.rs | 37 ++-- .../_helpers/cli/progress_reporters.rs | 2 +- .../bindings/_helpers/clone/clone_command.rs | 14 +- .../_helpers/clone/clone_execution.rs | 14 +- .../bindings/_helpers/clone/clone_pipeline.rs | 2 +- .../_helpers/command_runtime/invocation.rs | 46 ++-- .../command_runtime/warehouse_scope.rs | 30 ++- .../compile_attachment/declaration_scope.rs | 33 +-- .../compile_attachment/effective_config.rs | 44 ++-- .../compile_attachment/model_inputs.rs | 45 ++-- .../sql_expansion_context.rs | 8 +- .../_helpers/compile_command/reuse_attempt.rs | 18 +- .../_helpers/compile_driver/command.rs | 2 +- .../bindings/_helpers/compile_driver/diff.rs | 2 +- .../_helpers/compile_driver/project.rs | 5 +- .../_helpers/compile_driver/stage_work.rs | 3 + .../_helpers/compile_outputs/reuse.rs | 9 +- .../_helpers/compiled_project/loop_classes.rs | 8 +- .../_helpers/compiled_project/model_loop.rs | 2 +- .../_helpers/contract_command/command.rs | 32 +-- .../_helpers/contract_command/write.rs | 10 +- .../contracts/contract_diagnostics.rs | 6 +- .../_helpers/contracts/effective_config.rs | 6 +- .../bindings/_helpers/cost_command/command.rs | 14 +- .../_helpers/cost_report/functions.rs | 7 +- .../_helpers/cost_telemetry/context.rs | 2 +- .../_helpers/dbt_command/auto_init.rs | 18 +- .../bindings/_helpers/dbt_command/command.rs | 17 +- .../src/bindings/_helpers/dbt_command/init.rs | 20 +- .../bindings/_helpers/dbt_events/stream.rs | 2 +- .../_helpers/dbt_interop/event_stream.rs | 7 +- .../_helpers/dbt_interop/execution.rs | 4 +- .../bindings/_helpers/dbt_interop/graph.rs | 30 +-- .../bindings/_helpers/dbt_interop/pipeline.rs | 200 ++++++++++-------- .../_helpers/dbt_interop/pipeline_phases.rs | 55 +++-- .../_helpers/dbt_interop/pipeline_prologue.rs | 24 ++- .../_helpers/dbt_interop/plan_output.rs | 4 +- .../bindings/_helpers/dbt_interop/runner.rs | 2 +- .../_helpers/dbt_interop/runtime_execution.rs | 26 +-- .../_helpers/dbt_interop/sqlbuild_work.rs | 72 ++++--- .../bindings/_helpers/diagnostics/trackers.rs | 2 +- .../bindings/_helpers/diff_report/command.rs | 120 +++++------ .../bindings/_helpers/diff_report/query.rs | 93 ++++---- .../_helpers/diff_report/registration.rs | 5 +- .../_helpers/discovery/model_records.rs | 2 +- .../bindings/_helpers/discovery/snapshot.rs | 4 +- .../_helpers/event_exporting/dispatcher.rs | 2 +- .../_helpers/event_exporting/scope.rs | 4 +- .../_helpers/format_command/command.rs | 5 +- .../bindings/_helpers/format_command/files.rs | 2 +- .../bindings/_helpers/format_command/run.rs | 70 +++--- .../_helpers/format_command/selection.rs | 72 ++++--- .../_helpers/freshness/freshness_command.rs | 2 +- .../_helpers/graph/execution_gates.rs | 2 +- .../_helpers/hand_built/compiled_model.rs | 22 +- .../bindings/_helpers/init_command/command.rs | 5 +- .../_helpers/janitor/janitor_command.rs | 2 +- .../_helpers/lineage/lineage_cache.rs | 7 +- .../_helpers/lineage/lineage_command.rs | 122 +++++------ .../_helpers/lineage/project_lineage.rs | 2 +- .../_helpers/lineage/relation_fingerprint.rs | 2 +- .../bindings/_helpers/load/load_command.rs | 86 ++++---- .../bindings/_helpers/load/load_progress.rs | 13 +- .../_helpers/model_config/model_validator.rs | 5 +- .../_helpers/model_config/template_data.rs | 2 +- .../bindings/_helpers/plan_command/plan.rs | 2 +- .../bindings/_helpers/plan_command/rules.rs | 2 +- .../_helpers/planner_entries/clone_plans.rs | 4 +- .../planner_selection/model_selectors.rs | 6 +- .../_helpers/planner_selection/selectors.rs | 6 +- .../_helpers/planner_selection/unit_tests.rs | 2 +- .../_helpers/presentation_text/style.rs | 2 +- .../bindings/_helpers/query_command/dag.rs | 5 +- .../bindings/_helpers/query_command/query.rs | 5 +- .../src/bindings/_helpers/records/registry.rs | 9 - .../_helpers/refactor/refactor_command.rs | 2 +- .../_helpers/result_output/terminal_index.rs | 4 +- .../bindings/_helpers/rules/dsl_headers.rs | 6 +- .../src/bindings/_helpers/rules/lint_text.rs | 2 +- .../scenario_command/capture_command.rs | 5 +- .../_helpers/scenario_command/test_command.rs | 5 +- .../bindings/_helpers/scopes/scope_command.rs | 2 +- .../bindings/_helpers/seed_command/command.rs | 2 +- .../_helpers/sql_test_command/test_command.rs | 2 +- .../_helpers/sql_test_glue/authored_ctes.rs | 2 +- .../_helpers/workspace_commands/commands.rs | 7 +- .../unit/src/sqlbuild/lint/test_cli_format.py | 14 +- 109 files changed, 1345 insertions(+), 1064 deletions(-) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/analysis/binding_locations.rs b/crates/sqlbuild-python/src/bindings/_helpers/analysis/binding_locations.rs index 63c25a40e3..4ac680f26c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/analysis/binding_locations.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/analysis/binding_locations.rs @@ -19,7 +19,7 @@ type LocatedRow = ( /// Each reported diagnostic of `rows` through `(authored, query, cleaned)` and the expansion. #[pyfunction] -fn locate_model_binding_diagnostics( +pub(crate) fn locate_model_binding_diagnostics( rows: Vec, texts: (String, String, String), expansion: Option<(String, Vec>)>, 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 e377332d18..6a2f31c1a2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/analysis/explicit_references.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/analysis/explicit_references.rs @@ -26,11 +26,12 @@ 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::graph::execution_gates::model_python_hook_names; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::native_host::user_code::UserCodeHost; use crate::bindings::_helpers::records::construct::{enum_from_value, new_record, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; use crate::bindings::types::CompilerDetach; @@ -169,14 +170,11 @@ fn python_sql_owners<'py>( } } owners.extend(loader_owners(project, discovered_inputs)?); - let hook_names = registry::native(py, NativeAttr::ModelPythonHookNames)?; let mut attached: BTreeMap> = BTreeMap::new(); for model in native_field(project, "models")?.try_iter()? { let model: Bound<'py, PyAny> = model?; - let keywords: Bound<'py, PyDict> = PyDict::new(py); - keywords.set_item("model", &model)?; let model_name: String = native_field(&model, "name")?.extract()?; - for hook_name in hook_names.call((), Some(&keywords))?.try_iter()? { + for hook_name in model_python_hook_names(py, &model)?.try_iter()? { attached .entry(hook_name?.extract()?) .or_default() 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 c873af4531..02ae2be7b0 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 @@ -40,7 +40,7 @@ type ProofObjects<'py> = ( /// The outcome at `position` of a finished session as `ModelSqlAnalysis`, for hand-built inputs. #[pyfunction] #[pyo3(signature = (**request))] -fn session_outcome_analysis<'py>( +pub(crate) fn session_outcome_analysis<'py>( request: Option<&Bound<'py, PyDict>>, ) -> PyResult> { let request = Request::new("session_outcome_analysis", request)?; @@ -92,7 +92,7 @@ fn session_outcome_analysis<'py>( /// A native dynamic pivot proof row as Python's proof, None when absent. #[pyfunction] -fn dynamic_contract_proof<'py>(row: &Bound<'py, PyAny>) -> PyResult> { +pub(crate) fn dynamic_contract_proof<'py>(row: &Bound<'py, PyAny>) -> PyResult> { proof(row) } 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 0e481fe0a2..c9656e8280 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/audit_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/audit_command/command.rs @@ -11,20 +11,25 @@ use sqlbuild_core::audit_command::main::render_audit_summary::render_audit_summa use sqlbuild_core::audit_command::models::AuditCounts; use sqlbuild_core::cli_text::models::CliStyle; +use crate::bindings::_helpers::build_progress::audit_progress::AuditProgressReporter; +use crate::bindings::_helpers::cli::progress::current_native_progress_projector; +use crate::bindings::_helpers::cli::progress_reporters::ConnectionProgressReporter; +use crate::bindings::_helpers::cli::progress_text::write_execution_header; use crate::bindings::_helpers::command_runtime::invocation::resolve_reported_command_invocation; use crate::bindings::_helpers::compile_driver::pipeline_entries::build_static_pipeline_result; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, keywords, within}; use crate::bindings::_helpers::compile_driver::selection_phases::compile_static_command_context; +use crate::bindings::_helpers::cost_telemetry::context::CostContext; use crate::bindings::_helpers::dbt_interop::reference_resolver::resolve_external_sql_reference_resolver; use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_entries::command_plans::build_audit_command_plan; use crate::bindings::_helpers::records::construct::{new_record_with, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::result_output::documents::format_audit_execution_json; use crate::bindings::_helpers::result_output::documents::write_execution_json_output; +use crate::bindings::_helpers::result_output::event_writer::ExecutionEventWriter; const EXECUTION_COMMAND: &str = "sqb audit"; const PASS_OUTCOME: &str = "pass"; @@ -195,17 +200,21 @@ fn prepare_run<'py>( let worker_count: i64 = concurrency.min(i64::try_from(audit_count).unwrap_or(i64::MAX)); let stream: Bound<'py, PyAny> = native_field(invocation, "progress_stream")?; let use_color: Bound<'py, PyAny> = native_field(invocation, "use_color")?; - let progress: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::AuditProgressReporter)?, - &[ - ("entries", entries.clone()), - ("worker_limit", PyInt::new(py, worker_count).into_any()), - ("stream", stream.clone()), - ("use_color", use_color.clone()), - ], - )?; - let projector: Bound<'py, PyAny> = - registry::native(py, NativeAttr::CurrentNativeProgressProjector)?.call0()?; + let progress: Bound<'py, PyAny> = AuditProgressReporter::new( + py, + Some(keywords( + py, + &[ + ("entries", entries.clone()), + ("worker_limit", PyInt::new(py, worker_count).into_any()), + ("stream", stream.clone()), + ("use_color", use_color.clone()), + ], + )?), + ) + .and_then(|value| pyo3::Bound::new(py, value)) + .map(pyo3::Bound::into_any)?; + let projector: Bound<'py, PyAny> = current_native_progress_projector(py)?; if !projector.is_none() { for entry in entries.try_iter()? { call_with( @@ -214,14 +223,19 @@ fn prepare_run<'py>( )?; } } - let connection: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, - &[ - ("adapter_name", native_field(invocation, "adapter_name")?), - ("stream", stream), - ("use_color", use_color), - ], - )?; + let connection: Bound<'py, PyAny> = ConnectionProgressReporter::new( + py, + Some(keywords( + py, + &[ + ("adapter_name", native_field(invocation, "adapter_name")?), + ("stream", stream), + ("use_color", use_color), + ], + )?), + ) + .and_then(|value| pyo3::Bound::new(py, value)) + .map(pyo3::Bound::into_any)?; Ok(AuditRun { progress, connection, @@ -246,7 +260,8 @@ fn write_headers( header.set_item("target", py.None())?; header.set_item("concurrency", concurrency)?; header.set_item("use_color", &use_color)?; - registry::native(py, NativeAttr::WriteExecutionHeader)?.call((), Some(&header))?; + write_execution_header(py, Some(header.clone())) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; let entries: Bound<'_, PyAny> = native_field(pipeline, "plan_output")?.getattr("audit_entries")?; let mut models: BTreeSet = BTreeSet::new(); @@ -276,8 +291,9 @@ fn execute<'py>( let py: Python<'py> = invocation.py(); let plan: Bound<'py, PyAny> = native_field(pipeline, "plan_output")?; let project: Bound<'py, PyAny> = native_field(pipeline, "project")?; - let event_writer: Bound<'py, PyAny> = - registry::native(py, NativeAttr::ExecutionEventWriter)?.call0()?; + let event_writer: Bound<'py, PyAny> = ExecutionEventWriter::new(py, None) + .and_then(|value| pyo3::Bound::new(py, value)) + .map(pyo3::Bound::into_any)?; let relay: Bound<'py, AuditCallbackRelay> = Bound::new( py, AuditCallbackRelay { @@ -289,8 +305,7 @@ fn execute<'py>( let executed: PyResult>> = (|| { run.progress .call_method1("set_result_callback", (native_field(&relay, "on_result")?,))?; - let projector: Bound<'py, PyAny> = - registry::native(py, NativeAttr::CurrentNativeProgressProjector)?.call0()?; + let projector: Bound<'py, PyAny> = current_native_progress_projector(py)?; if !projector.is_none() { let ordinals: Bound<'py, PyDict> = PyDict::new(py); let mut total: usize = 0; @@ -313,16 +328,21 @@ fn execute<'py>( .call_method1("__truediv__", ("executions",))? .call_method1("__truediv__", (&run_id,))? .call_method1("__truediv__", ("statements.jsonl",))?; - let scope: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::CostContext)?.getattr("scope")?, - &[ - ("run_id", run_id.clone()), - ("resource_type", text(py, "run")), - ("resource_name", text(py, "audit")), - ("ledger_path", ledger_path), - ("phase", text(py, "audit")), - ], - )?; + let scope: Bound<'py, PyAny> = CostContext::scope( + py, + Some(keywords( + py, + &[ + ("run_id", run_id.clone()), + ("resource_type", text(py, "run")), + ("resource_name", text(py, "audit")), + ("ledger_path", ledger_path), + ("phase", text(py, "audit")), + ], + )?), + ) + .and_then(|scope| Bound::new(py, scope))? + .into_any(); within(&scope, |_| { let callbacks: Bound<'py, PyAny> = call_with( &host::entry(py, ExecutorHost::AuditPipelineCallbacks)?, @@ -487,7 +507,7 @@ fn complete( /// Run one `sqb audit` request. #[pyfunction] #[pyo3(signature = (*, request))] -fn run_audit_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { +pub(crate) fn run_audit_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { let invocation: Bound<'_, PyAny> = resolve_reported_command_invocation( request, &record_class(py, RecordId::AuditInvocation)?, 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 3dad08bd91..31db14c22d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/command.rs @@ -16,12 +16,11 @@ use crate::bindings::_helpers::build_command::planning::{ write_compile_target, write_plan_text, }; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, within}; +use crate::bindings::_helpers::diagnostics::trackers::BuildPhaseTimingTracker; 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::fields::field as native_field; -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(); @@ -89,9 +88,10 @@ fn run_within_limits( /// Run one `sqb build` request. #[pyfunction] #[pyo3(signature = (*, request))] -fn run_build_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { - let tracker: Bound<'_, PyAny> = - registry::native(py, NativeAttr::BuildPhaseTimingTracker)?.call0()?; +pub(crate) fn run_build_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { + let tracker: Bound<'_, PyAny> = BuildPhaseTimingTracker::new(py, None) + .and_then(|value| pyo3::Bound::new(py, value)) + .map(pyo3::Bound::into_any)?; let reporting: Bound<'_, PyAny> = call_with( &host::entry(py, ExecutorHost::ProcessResourceReporting)?, &[("enabled", native_field(request, "debug")?)], 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 1d9fb5d2da..0b05d835a5 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/completion.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/completion.rs @@ -31,10 +31,10 @@ use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::result_output::documents::format_build_execution_json; use crate::bindings::_helpers::result_output::documents::format_degradable_build_execution_json; +use crate::bindings::_helpers::result_output::documents::write_execution_json_output; const SUCCESS_STATUS: &str = "success"; const PENDING_MESSAGE: &str = "Build cost collection has not completed."; @@ -176,21 +176,12 @@ fn run_id_for<'py>( } fn write_json(context: &BuildContext<'_>, payload: Bound<'_, PyAny>) -> PyResult<()> { - call_with( - ®istry::native(context.py(), NativeAttr::WriteExecutionJsonOutput)?, - &[ - ("payload", payload), - ( - "json_output", - native_field(&context.request, "json_output")?, - ), - ( - "json_output_path", - native_field(&context.request, "json_output_path")?, - ), - ], - )?; - Ok(()) + write_execution_json_output( + context.py(), + payload, + native_field(&context.request, "json_output")?.extract()?, + native_field(&context.request, "json_output_path")?, + ) } /// Persist and emit a completed zero-cost record when the plan has no work. 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 ca77b10e23..3fc31ed6a5 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 @@ -13,14 +13,22 @@ 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::build_command::policies::enforce_model_execution_limit_binding; +use crate::bindings::_helpers::clone::clone_pipeline::run_clone_pipeline_with_options; use crate::bindings::_helpers::compile_driver::project::build_compiled_project; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; +use crate::bindings::_helpers::contracts::spec_resolution::resolve_target_config; +use crate::bindings::_helpers::dbt_interop::reference_resolver::resolve_external_sql_reference_resolver; use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::planner_entries::clone_plans::{ + resolve_clone_boundary, resolve_skipped_view_chain, +}; +use crate::bindings::_helpers::planner_scope::scope::build_planner_scope; +use crate::bindings::_helpers::planner_selection::unit_tests::split_unit_test_selectors; use crate::bindings::_helpers::records::construct::{enum_member, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::ids::RecordId; const VIEW_MATERIALIZATION: &str = "view"; const RECREATED_TYPES: [&str; 2] = ["udf", "table_fn"]; @@ -102,20 +110,27 @@ fn membership<'py>( PyFrozenSet::new(py, &members)?.getattr("__contains__") } +/// A clone-plan traversal from selected keys over upstream dependencies. +type TraversalFunction = + for<'py> fn(Python<'py>, Option>) -> PyResult>; + fn traversed_names<'py>( scope: &Bound<'py, PyAny>, - function: NativeAttr, + function: TraversalFunction, (is_clonable, is_view): (&Bound<'py, PyAny>, &Bound<'py, PyAny>), ) -> PyResult> { let py: Python<'py> = scope.py(); - let keys: Bound<'py, PyAny> = call_with( - ®istry::native(py, function)?, - &[ - ("selected", native_field(scope, "selected_keys")?), - ("upstream", native_field(scope, "upstream_deps")?), - ("is_clonable", is_clonable.clone()), - ("is_view", is_view.clone()), - ], + let keys: Bound<'py, PyFrozenSet> = function( + py, + Some(keywords( + py, + &[ + ("selected", native_field(scope, "selected_keys")?), + ("upstream", native_field(scope, "upstream_deps")?), + ("is_clonable", is_clonable.clone()), + ("is_view", is_view.clone()), + ], + )?), )?; let mut names: Vec = Vec::new(); for key in keys.try_iter()? { @@ -159,8 +174,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, NativeAttr::ResolveCloneBoundary, callbacks)?, - traversed_names(scope, NativeAttr::ResolveSkippedViewChain, callbacks)?, + traversed_names(scope, resolve_clone_boundary, callbacks)?, + traversed_names(scope, resolve_skipped_view_chain, callbacks)?, selected_executable_model_count(scope)?, )) } @@ -174,26 +189,11 @@ fn resolve_defer_clone_selection( defer_clone_selection(scope) } -fn native<'py>( - py: Python<'py>, - name: NativeAttr, - fields: &[(&str, Bound<'py, PyAny>)], -) -> PyResult> { - call_with(®istry::native(py, name)?, fields) -} - fn external_resolver<'py>( project_dir: &Bound<'py, PyAny>, discovered: &Bound<'py, PyAny>, ) -> PyResult> { - native( - project_dir.py(), - NativeAttr::ResolveExternalSqlReferenceResolver, - &[ - ("project_dir", project_dir.clone()), - ("discovered_inputs", discovered.clone()), - ], - ) + resolve_external_sql_reference_resolver(project_dir.clone(), discovered.clone()) } /// The resolved defer-clone prephase: what to clone, from where, into which target. @@ -224,20 +224,11 @@ fn clone_input<'py>( let project: Bound<'py, PyAny> = native_field(pipeline, "destination_project")?; let destination_models: Bound<'py, PyAny> = native_field(pipeline, "destination_model_entries")?; - let target: Bound<'py, PyAny> = native( + let target: Bound<'py, PyAny> = resolve_target_config( py, - NativeAttr::ResolveTargetConfig, - &[ - ( - "project_config", - native_field(&prephase.discovered, "project_config")?, - ), - ( - "local_config", - native_field(&prephase.discovered, "local_config")?, - ), - ("target_name", prephase.destination.clone()), - ], + &native_field(&prephase.discovered, "project_config")?, + &native_field(&prephase.discovered, "local_config")?, + &prephase.destination.clone(), )?; let mut selected: Vec> = Vec::new(); for entry in destination_models.try_iter()? { @@ -427,9 +418,8 @@ fn clone_boundaries<'py>( .getattr("on_progress")?; let start: Instant = Instant::now(); let request: &Bound<'py, PyAny> = &context.request; - let pipeline: Bound<'py, PyAny> = native( + let pipeline: Bound<'py, PyAny> = run_clone_pipeline_with_options(Some(&keywords( py, - NativeAttr::RunClonePipelineWithOptions, &[ ("discovered_inputs", prephase.discovered.clone()), ("adapter", prephase.adapter.clone()), @@ -470,7 +460,7 @@ fn clone_boundaries<'py>( external_resolver(&prephase.project_dir, &prephase.discovered)?, ), ], - )?; + )?))?; progress( &on_progress, &format!( @@ -572,14 +562,10 @@ pub(crate) fn run_defer_clone_boundary_prephase( let py: Python<'_> = context.py(); let (request, invocation) = (&context.request, &context.invocation); let discovered: Bound<'_, PyAny> = native_field(invocation, "discovered_inputs")?; - let split: Bound<'_, PyAny> = native( - py, - NativeAttr::SplitUnitTestSelectors, - &[ - ("select", native_field(request, "select")?), - ("exclude", native_field(request, "exclude")?), - ("discovered_inputs", discovered.clone()), - ], + let split: Bound<'_, PyAny> = split_unit_test_selectors( + &native_field(request, "select")?, + &native_field(request, "exclude")?, + &discovered.clone(), )?; if !split .getattr("sql_test_selection")? @@ -618,37 +604,42 @@ pub(crate) fn run_defer_clone_boundary_prephase( ], )?), )?; - let scope: Bound<'_, PyAny> = native( + let scope: Bound<'_, PyAny> = build_planner_scope( py, - NativeAttr::BuildPlannerScope, - &[ - ("project", project.clone()), - ("select", native_field(&split, "select")?), - ("exclude", native_field(&split, "exclude")?), - ( - "auto_load_sources", - native_field(invocation, "should_load_sources")?, - ), - ], + Some(keywords( + py, + &[ + ("project", project.clone()), + ("select", native_field(&split, "select")?), + ("exclude", native_field(&split, "exclude")?), + ( + "auto_load_sources", + native_field(invocation, "should_load_sources")?, + ), + ], + )?), )?; let (boundary, view_chain, model_count): (Vec, Vec, usize) = defer_clone_selection(&scope)?; - native( + enforce_model_execution_limit_binding( py, - NativeAttr::EnforceModelExecutionLimit, - &[ - ("model_count", model_count.into_pyobject(py)?.into_any()), - ( - "target_name", - native_field(invocation, "effective_target_name")?, - ), - ("limits", native_field(invocation, "execution_limits")?), - ( - "refusal_note", - EXECUTION_LIMIT_BUILD_NOTE.into_bound_py_any(py)?, - ), - ], - )?; + Some(keywords( + py, + &[ + ("model_count", model_count.into_pyobject(py)?.into_any()), + ( + "target_name", + native_field(invocation, "effective_target_name")?, + ), + ("limits", native_field(invocation, "execution_limits")?), + ( + "refusal_note", + EXECUTION_LIMIT_BUILD_NOTE.into_bound_py_any(py)?, + ), + ], + )?), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; let mut select: Vec = boundary; select.extend(view_chain); run_prephase( 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 27bad89003..3ef9c3b6ea 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/execution.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/execution.rs @@ -14,24 +14,27 @@ use crate::bindings::_helpers::build_command::lifecycle::{ use crate::bindings::_helpers::build_command::support::{ boolean, display_text, emit, frozenset, joined, keyword_relay, line_writer, none, text, tuple, }; +use crate::bindings::_helpers::build_progress::build_callbacks::BuildProgressCallbacks; 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::check::check_results::write_check_results; +use crate::bindings::_helpers::cli::progress_reporters::ConnectionProgressReporter; use crate::bindings::_helpers::cli::progress_text::write_execution_header; use crate::bindings::_helpers::command_runtime::warehouses::describe_connection_warehouse; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, keywords, within}; use crate::bindings::_helpers::compile_driver::relation_targets::build_project_relation_targets; +use crate::bindings::_helpers::cost_telemetry::context::CostContext; 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::planner_resolution::cursor_overrides::type_cursor_overrides; use crate::bindings::_helpers::records::construct::new_record; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::ids::RecordId; const EXECUTION_COMMAND: &str = "sqb build"; const SUCCESS_STATUS: &str = "success"; @@ -177,13 +180,8 @@ fn cursors<'py>( let py: Python<'py> = context.py(); let mut overrides: Bound<'py, PyAny> = native_field(plan, "cursor_overrides")?; if !overrides.is_truthy()? { - overrides = call_with( - ®istry::native(py, NativeAttr::TypeCursorOverrides)?, - &[( - "cursor_overrides", - native_field(&context.request, "cursor_overrides")?, - )], - )?; + overrides = + type_cursor_overrides(&native_field(&context.request, "cursor_overrides")?, &[])?; } if !overrides.is_truthy()? { overrides = new_record(py, RecordId::CursorOverrides, (), None)?; @@ -205,19 +203,24 @@ pub(crate) fn prepare_build_execution<'py>( let py: Python<'py> = context.py(); let plan: Bound<'py, PyAny> = native_field(pipeline, "plan_output")?; let debug: bool = context.request_flag("debug")? || context.request_flag("json_output")?; - let callbacks: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::BuildProgressCallbacks)?, - &[ - ("plan", plan.clone()), - ("use_color", boolean(py, context.use_color)), - ("verbose", native_field(&context.request, "verbose")?), - ("debug", boolean(py, debug)), - ( - "event_output_path", - native_field(&context.request, "event_output_path")?, - ), - ], - )?; + let callbacks: Bound<'py, PyAny> = BuildProgressCallbacks::new( + py, + Some(keywords( + py, + &[ + ("plan", plan.clone()), + ("use_color", boolean(py, context.use_color)), + ("verbose", native_field(&context.request, "verbose")?), + ("debug", boolean(py, debug)), + ( + "event_output_path", + native_field(&context.request, "event_output_path")?, + ), + ], + )?), + ) + .and_then(|value| pyo3::Bound::new(py, value)) + .map(pyo3::Bound::into_any)?; let concurrency: Bound<'py, PyAny> = context.concurrency(&native_field(pipeline, "project")?)?; if context.verbose { @@ -238,19 +241,24 @@ pub(crate) fn prepare_build_execution<'py>( ) .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; } - let connection_progress: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, - &[ - ("adapter_name", context.get("adapter_name")?), - ("stream", context.stream.clone()), - ( - "blank_line_before_start", - boolean(py, native_field(&plan, "source_load_entries")?.is_truthy()?), - ), - ("blank_line_after_complete", boolean(py, true)), - ("use_color", boolean(py, context.use_color)), - ], - )?; + let connection_progress: Bound<'py, PyAny> = ConnectionProgressReporter::new( + py, + Some(keywords( + py, + &[ + ("adapter_name", context.get("adapter_name")?), + ("stream", context.stream.clone()), + ( + "blank_line_before_start", + boolean(py, native_field(&plan, "source_load_entries")?.is_truthy()?), + ), + ("blank_line_after_complete", boolean(py, true)), + ("use_color", boolean(py, context.use_color)), + ], + )?), + ) + .and_then(|value| pyo3::Bound::new(py, value)) + .map(pyo3::Bound::into_any)?; let cursors: [Bound<'py, PyAny>; 4] = cursors(context, &plan)?; let inputs: LifecycleInputs<'py> = LifecycleInputs { cursors: cursors.clone(), @@ -602,7 +610,6 @@ pub(crate) fn execute_direct_build<'py>( } else { context.get("adapter_name")? }; - 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")?; @@ -620,20 +627,26 @@ pub(crate) fn execute_direct_build<'py>( )?, )?; options.set_item("phase", "build")?; - let scope: Bound<'py, PyAny> = cost_context.call_method("scope", (), Some(&options))?; + let scope: Bound<'py, PyAny> = + Bound::new(py, CostContext::scope(py, Some(options))?)?.into_any(); within(&scope, |_| { let preparation: Preparation<'py> = prepare_build_execution(context, pipeline, providers)?; let ran: PyResult<(Outcome<'py>, Bound<'py, PyAny>)> = (|| { let outcome: Outcome<'py> = execute_build_plan(context, pipeline, &preparation, providers)?; - let checks_scope: Bound<'py, PyAny> = call_with( - &native_field(&cost_context, "resource_scope")?, - &[ - ("resource_type", text(py, "run")), - ("resource_name", resource_name.clone()), - ("phase", text(py, "post_build_checks")), - ], - )?; + let checks_scope: Bound<'py, PyAny> = CostContext::resource_scope( + py, + Some(keywords( + py, + &[ + ("resource_type", text(py, "run")), + ("resource_name", resource_name.clone()), + ("phase", text(py, "post_build_checks")), + ], + )?), + ) + .and_then(|scope| Bound::new(py, scope))? + .into_any(); let checks: Bound<'py, PyAny> = within(&checks_scope, |_| { run_post_build_python_checks(context, pipeline, (&outcome, &preparation), providers) })?; 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 c483955fa6..5f6df17539 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/invocation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/invocation.rs @@ -9,20 +9,21 @@ use sqlbuild_core::build_command::models::DurationLimit; use crate::bindings::_helpers::build_command::policies::build_command_error; use crate::bindings::_helpers::build_command::support::{boolean, or_none, text}; +use crate::bindings::_helpers::cli::progress_reporters::ConnectionProgressReporter; +use crate::bindings::_helpers::cli::progress_reporters::PlanningProgressReporter; use crate::bindings::_helpers::command_runtime::adapters::resolve_cli_adapter; use crate::bindings::_helpers::command_runtime::connection::resolve_project_connection_config; use crate::bindings::_helpers::compile_attachment::effective_config::build_effective_settings_config; use crate::bindings::_helpers::compile_driver::discovery::discover_project_inputs; +use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::compile_driver::python_calls::keywords; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; use crate::bindings::_helpers::contracts::spec_resolution::resolve_effective_adapter_name; use crate::bindings::_helpers::contracts::spec_resolution::resolve_target_config; use crate::bindings::_helpers::contracts::spec_resolution::resolve_target_name; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; use crate::bindings::_helpers::records::construct::{new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::ids::RecordId; const DEFER_CONFLICT_CODE: &str = "C408"; @@ -176,21 +177,26 @@ pub(crate) fn resolve_build_context<'py>( } else { "stdout" })?; - let connection_progress: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, - &[ - ("adapter_name", text(py, &adapter_name)), - ("stream", stream.clone()), - ("use_color", boolean(py, use_color)), - ], - )?; - let planning_progress: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::PlanningProgressReporter)?, - &[ - ("stream", stream.clone()), - ("use_color", boolean(py, use_color)), - ], - )?; + let connection_progress: Bound<'py, PyAny> = ConnectionProgressReporter::new( + py, + Some(keywords( + py, + &[ + ("adapter_name", text(py, &adapter_name)), + ("stream", stream.clone()), + ("use_color", boolean(py, use_color)), + ], + )?), + ) + .and_then(|value| pyo3::Bound::new(py, value)) + .map(pyo3::Bound::into_any)?; + let planning_progress: Bound<'py, PyAny> = PlanningProgressReporter::new( + py, + &stream.clone(), + boolean(py, use_color).extract::()?, + ) + .and_then(|value| pyo3::Bound::new(py, value)) + .map(pyo3::Bound::into_any)?; let load_sources: Bound<'py, PyAny> = native_field(request, "load_sources")?; let should_load_sources: bool = native_field(request, "reload_sources")?.is_truthy()? || if load_sources.is_none() { 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 44dca30ee9..14e6df5f0b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/policies.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/policies.rs @@ -264,7 +264,7 @@ pub(crate) fn enforce_plan_safety_policies_binding<'py>( /// Reject a build whose expanded model count exceeds its target policy. #[pyfunction] #[pyo3(name = "enforce_model_execution_limit", signature = (**options))] -fn enforce_model_execution_limit_binding<'py>( +pub(crate) fn enforce_model_execution_limit_binding<'py>( py: Python<'py>, options: Option>, ) -> Result<(), PyErr> { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/build_progress/audit_progress.rs b/crates/sqlbuild-python/src/bindings/_helpers/build_progress/audit_progress.rs index b98d01ab05..2fde1ae5fe 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_progress/audit_progress.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_progress/audit_progress.rs @@ -175,7 +175,7 @@ impl AuditProgressReporter { impl AuditProgressReporter { #[new] #[pyo3(signature = (**options))] - fn new(py: Python<'_>, options: Option>) -> PyResult { + pub(crate) fn new(py: Python<'_>, options: Option>) -> PyResult { let arguments = KeywordArguments::read( py, "AuditProgressReporter", 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 6f5f280900..773e6b0a75 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 @@ -31,8 +31,7 @@ use crate::bindings::_helpers::build_progress::terminal::{ }; use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported, within}; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::NativeAttr; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::result_output::event_writer::ExecutionEventWriter; const RUNTIME_LOGGER: &str = "sqlbuild.executor.runtime"; const PASS_OUTCOME: &str = "pass"; @@ -379,7 +378,7 @@ fn statement(telemetry: &Bound<'_, PyAny>) -> PyResult { impl BuildProgressCallbacks { #[new] #[pyo3(signature = (**options))] - fn new(py: Python<'_>, options: Option>) -> PyResult { + pub(crate) fn new(py: Python<'_>, options: Option>) -> PyResult { let arguments = KeywordArguments::read( py, "BuildProgressCallbacks", @@ -406,11 +405,11 @@ impl BuildProgressCallbacks { } else { py.None().into_bound(py) }; - let writer_options: Bound<'_, PyDict> = PyDict::new(py); - writer_options.set_item("path", path)?; - let event_writer: Bound<'_, PyAny> = - registry::native(py, NativeAttr::ExecutionEventWriter)? - .call((), Some(&writer_options))?; + let event_writer: Bound<'_, PyAny> = Bound::new( + py, + ExecutionEventWriter::new(py, Some(path).filter(|value| !value.is_none()))?, + )? + .into_any(); 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 63aaf8c68f..94ddce2b9d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_progress/terminal.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_progress/terminal.rs @@ -5,10 +5,9 @@ use pyo3::types::{PyBool, PyDict, PyDictMethods, PyTuple}; use pyo3::{IntoPyObjectExt, pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::build_progress::transient_lines::shared_coordinator; +use crate::bindings::_helpers::cli::progress::current_native_progress_projector; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; use crate::bindings::_helpers::records::fields::field as native_field; -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; @@ -50,8 +49,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> = - registry::native(py, NativeAttr::CurrentNativeProgressProjector)?.call0()?; + let projector: Bound<'_, PyAny> = current_native_progress_projector(py)?; Ok((!projector.is_none()).then(|| projector.unbind())) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/build_progress/transient_lines.rs b/crates/sqlbuild-python/src/bindings/_helpers/build_progress/transient_lines.rs index e746a615ab..72ffe392e2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_progress/transient_lines.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_progress/transient_lines.rs @@ -129,7 +129,7 @@ pub(crate) fn shared_coordinator(py: Python<'_>) -> PyResult, ) -> PyResult> { shared_coordinator(py) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/build_progress/transient_status.rs b/crates/sqlbuild-python/src/bindings/_helpers/build_progress/transient_status.rs index 702e84608b..2d34cf8a75 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_progress/transient_status.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_progress/transient_status.rs @@ -144,7 +144,7 @@ impl TransientStatusReporter { impl TransientStatusReporter { #[new] #[pyo3(signature = (*, stream, use_color = false, enabled = true))] - fn new( + pub(crate) fn new( py: Python<'_>, stream: &Bound<'_, PyAny>, use_color: bool, 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 b11fdbce05..701418fd05 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cache/native_store.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cache/native_store.rs @@ -99,8 +99,8 @@ pub(crate) fn compiled_code_identity(py: Python<'_>) -> PyResult { /// `compiled_code_identity`, looked up on the extension module so tests can replace it. pub(crate) fn current_code_identity(py: Python<'_>) -> PyResult { - registry::native(py, NativeAttr::CompiledCodeIdentity)? - .call0()? + crate::bindings::_helpers::cache::native_store::compiled_code_identity(py) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))? .extract() } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cache/sql_test_scan_store.rs b/crates/sqlbuild-python/src/bindings/_helpers/cache/sql_test_scan_store.rs index a5d7a6f9b5..8842001e16 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cache/sql_test_scan_store.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cache/sql_test_scan_store.rs @@ -126,7 +126,7 @@ pub(crate) struct SqlTestScanCache { impl SqlTestScanCache { #[new] #[pyo3(signature = (*, cache_dir))] - fn new(cache_dir: Option) -> Self { + pub(crate) fn new(cache_dir: Option) -> Self { Self { state: Mutex::new((cache_dir, None)), } 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 84efb5a408..1769d2a6de 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/check/check_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/check/check_command.rs @@ -651,7 +651,7 @@ fn complete<'py>( /// Run one `sqb check` request. #[pyfunction] #[pyo3(signature = (*, request))] -fn run_check_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { +pub(crate) fn run_check_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { let invocation: Bound<'_, PyAny> = resolve_reported_command_invocation( request, &record_class(py, RecordId::CheckInvocation)?, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cli/dispatch.rs b/crates/sqlbuild-python/src/bindings/_helpers/cli/dispatch.rs index 5da47cec7a..3cfd621d70 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cli/dispatch.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cli/dispatch.rs @@ -2,12 +2,43 @@ use std::path::PathBuf; +use crate::bindings::_helpers::audit_command::command::run_audit_command; +use crate::bindings::_helpers::build_command::command::run_build_command; +use crate::bindings::_helpers::check::check_command::run_check_command; use crate::bindings::_helpers::cli::launch_state::withhold_launch_record; +use crate::bindings::_helpers::clone::clone_command::run_clone_command; use crate::bindings::_helpers::command_runtime::warehouse_scope::command_warehouse_scope; use crate::bindings::_helpers::command_runtime::warehouses::validate_cli_warehouse; +use crate::bindings::_helpers::compile_command::reuse_attempt::run_compile; +use crate::bindings::_helpers::contract_command::command::run_contract_command; +use crate::bindings::_helpers::cost_command::command::run_cost_command; +use crate::bindings::_helpers::dbt_command::command::run_dbt_command; +use crate::bindings::_helpers::dbt_command::init::run_dbt_init_command; +use crate::bindings::_helpers::debug_command::run_debug::run_debug_command; +use crate::bindings::_helpers::diff_report::command::run_diff_command; use crate::bindings::_helpers::diff_report::support::cli_error; +use crate::bindings::_helpers::format_command::command::run_format_command; +use crate::bindings::_helpers::freshness::freshness_command::run_freshness_command; +use crate::bindings::_helpers::init_command::command::run_init_command; +use crate::bindings::_helpers::janitor::janitor_command::run_janitor_command; +use crate::bindings::_helpers::lineage::lineage_command::run_lineage_command; +use crate::bindings::_helpers::load::load_command::run_load_command; +use crate::bindings::_helpers::plan_command::plan::run_plan_command; +use crate::bindings::_helpers::plan_command::rules::run_rules_command; +use crate::bindings::_helpers::query_command::dag::run_dag_command; +use crate::bindings::_helpers::query_command::query::run_query_command; use crate::bindings::_helpers::records::construct::{new_record_with, record_class}; +use crate::bindings::_helpers::refactor::refactor_command::run_refactor_command; +use crate::bindings::_helpers::scenario_command::capture_command::run_scenario_capture_command; +use crate::bindings::_helpers::scenario_command::test_command::run_scenario_test_command; +use crate::bindings::_helpers::scopes::scope_command::run_scope_command; +use crate::bindings::_helpers::seed_command::command::run_seed_command; +use crate::bindings::_helpers::sql_test_command::test_command::run_test_command; +use crate::bindings::_helpers::workspace_commands::commands::{ + run_playground_command, run_skills_command, +}; use pyo3::IntoPyObjectExt; +use pyo3::exceptions::{PyAttributeError, PyTypeError}; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python}; use pyo3::types::{PyDict, PyDictMethods, PyString, PyTuple}; use sqlbuild_cli::diff_report::errors::DiffUserError; @@ -22,7 +53,7 @@ use sqlbuild_core::text::main::python_strip::python_strip; use crate::bindings::_helpers::compile_driver::python_calls::{imported, within}; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; const PATHLIB_MODULE: &str = "pathlib"; @@ -258,19 +289,17 @@ fn call_route<'py>( let py: Python<'py> = invocation.py(); let mut positional: Vec> = Vec::new(); let keywords: Bound<'py, PyDict> = PyDict::new(py); - let (callable, positional_count) = match handler { - Some(handler) => (handler, route.shape.slot_positional), + let positional_count: usize = match &handler { + Some(_) => route.shape.slot_positional, None => { - let function: NativeAttr = registry::attribute_named(route.implementation)?; if let Some(extra) = route.implementation_extra { keywords.set_item(extra.name, invocation.value(extra.source)?)?; } - let count: usize = if route.shape.implementation_keywords { + if route.shape.implementation_keywords { 0 } else { route.shape.slot_positional - }; - (registry::native(py, function)?, count) + } } }; for (index, field) in route.arguments.iter().enumerate() { @@ -281,7 +310,79 @@ fn call_route<'py>( keywords.set_item(field.name, value)?; } } - callable.call(PyTuple::new(py, positional)?, Some(&keywords)) + match handler { + Some(handler) => handler.call(PyTuple::new(py, positional)?, Some(&keywords)), + None => run_implementation(route.implementation, (positional, keywords)), + } +} + +/// The argument passed first by position or under `name`, or `None`. +fn argument<'py>( + (positional, keywords): &(Vec>, Bound<'py, PyDict>), + name: &str, +) -> PyResult>> { + match positional.first() { + Some(value) => Ok(Some(value.clone())), + None => keywords.get_item(name), + } +} + +/// Run the native command implementation `name` with the route's arguments. +fn run_implementation<'py>( + name: &str, + arguments: (Vec>, Bound<'py, PyDict>), +) -> PyResult> { + let py: Python<'py> = arguments.1.py(); + let request = || -> PyResult> { + argument(&arguments, "request")? + .ok_or_else(|| PyTypeError::new_err(format!("{name}() missing argument 'request'"))) + }; + let options = || Some(arguments.1.clone()); + let code: i32 = match name { + "run_audit_command" => run_audit_command(py, &request()?)?, + "run_build_command" => run_build_command(py, &request()?)?, + "run_check_command" => run_check_command(py, &request()?)?, + "run_clone_command" => run_clone_command(py, &request()?)?, + "run_compile" => return run_compile(&request()?), + "run_contract_command" => run_contract_command(py, &request()?)?, + "run_cost_command" => run_cost_command(py, &request()?)?, + "run_dag_command" => run_dag_command(py, options())?, + "run_dbt_command" => run_dbt_command(py, options())?, + "run_dbt_init_command" => run_dbt_init_command(py, &request()?)?, + "run_debug_command" => run_debug_command(py, options())?, + "run_diff_command" => run_diff_command(py, &request()?)?, + "run_format_command" => run_format_command(py, options())?, + "run_freshness_command" => run_freshness_command(py, &request()?)?, + "run_init_command" => run_init_command( + py, + argument(&arguments, "project_dir")?.filter(|value| !value.is_none()), + )?, + "run_janitor_command" => run_janitor_command(py, &request()?)?, + "run_lineage_command" => run_lineage_command(py, &request()?)?, + "run_load_command" => run_load_command(py, &request()?)?, + "run_plan_command" => run_plan_command(&request()?)?, + "run_playground_command" => run_playground_command(py, &request()?)?, + "run_query_command" => run_query_command(py, options())?, + "run_refactor_command" => run_refactor_command(py, &request()?)?, + "run_rules_command" => run_rules_command(&request()?)?, + "run_scenario_capture_command" => run_scenario_capture_command(py, &request()?)?, + "run_scenario_test_command" => run_scenario_test_command(py, &request()?)?, + "run_scope_command" => run_scope_command( + py, + &request()?, + arguments.1.get_item("load_scope_index")?, + arguments.1.get_item("output_stream")?, + )?, + "run_seed_command" => run_seed_command(py, &request()?)?, + "run_skills_command" => run_skills_command(py, options())?, + "run_test_command" => run_test_command(py, &request()?)?, + _ => { + return Err(PyAttributeError::new_err(format!( + "module 'sqlbuild._native' has no attribute '{name}'" + ))); + } + }; + code.into_bound_py_any(py) } fn routed_command<'py>( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cli/entry.rs b/crates/sqlbuild-python/src/bindings/_helpers/cli/entry.rs index 9dfe4824df..c1d76e4726 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cli/entry.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cli/entry.rs @@ -12,12 +12,16 @@ use sqlbuild_cli::dispatch::main::failure_report::failure_report; use sqlbuild_cli::dispatch::main::invocation_policy::invocation_policy; use sqlbuild_cli::dispatch::models::{CommandFacts, CommandFailure, InvocationPolicy}; +use crate::bindings::_helpers::build_progress::transient_lines::shared_transient_line_coordinator; use crate::bindings::_helpers::cli::argv::parse_cli_invocation; 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_command::report::format_compile_error_json; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, within}; +use crate::bindings::_helpers::compile_driver::python_calls::{ + call_with, imported, keywords, within, +}; +use crate::bindings::_helpers::diff_report::registration::render_diff_error_json; use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::facade::FacadeHost; use crate::bindings::_helpers::native_host::host; @@ -25,7 +29,7 @@ use crate::bindings::_helpers::output_capture::records::invocation_context; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; use crate::bindings::_helpers::records::construct::record_class; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::workspace_commands::commands::maintain_sqlbuild_skills; use sqlbuild_cli::launcher::constants::LAUNCH_RECORD_OPTION; @@ -186,10 +190,8 @@ fn render_diff_error<'py>( py: Python<'py>, fields: &[(&str, Bound<'py, PyAny>)], ) -> PyResult> { - call_with( - ®istry::native(py, NativeAttr::RenderDiffErrorJson)?, - fields, - ) + render_diff_error_json(py, Some(keywords(py, fields)?)) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py)) } /// The diff JSON error document, and the publication error that replaces `error` if any. @@ -452,8 +454,8 @@ fn handled_dispatch<'py>(invocation: &Invocation<'py>) -> PyResult> = observed_dispatch(invocation, policy); - registry::native(py, NativeAttr::SharedTransientLineCoordinator)? - .call0()? + shared_transient_line_coordinator(py) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))? .call_method0("clear_active")?; let error: PyErr = match dispatched { Ok(exit_code) => return Ok(exit_code), 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 78d618c392..5f17fdc86d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cli/launch_state.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cli/launch_state.rs @@ -3,8 +3,8 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyBool, PyDict, PyDictMethods}; -use crate::bindings::_helpers::records::ids::NativeAttr; -use crate::bindings::_helpers::records::registry; +use pyo3::Py; +use pyo3::sync::PyOnceLock; /// The module attributes holding the offered path's and the withheld flag's `ContextVar`s. const OFFERED_ATTRIBUTE: &str = "_launch_record_offered"; @@ -12,29 +12,30 @@ const WITHHELD_ATTRIBUTE: &str = "_launch_record_withheld"; const OFFERED_NAME: &str = "sqlbuild_launch_record"; const WITHHELD_NAME: &str = "sqlbuild_launch_record_withheld"; +/// The offered path's and the withheld flag's `ContextVar`s. +static OFFERED: PyOnceLock> = PyOnceLock::new(); +static WITHHELD: PyOnceLock> = PyOnceLock::new(); + /// 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)); - registry::native(py, NativeAttr::LaunchRecordOffered)?.call_method1("set", (path,))?; - registry::native(py, NativeAttr::LaunchRecordWithheld)?.call_method1("set", (false,))?; + offered(py)?.call_method1("set", (path,))?; + withheld(py)?.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<()> { - registry::native(py, NativeAttr::LaunchRecordWithheld)?.call_method1("set", (true,))?; + withheld(py)?.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 registry::native(py, NativeAttr::LaunchRecordWithheld)? - .call_method0("get")? - .is_truthy()? - { + if withheld(py)?.call_method0("get")?.is_truthy()? { return Ok(py.None().into_bound(py)); } - registry::native(py, NativeAttr::LaunchRecordOffered)?.call_method0("get") + offered(py)?.call_method0("get") } fn context_variable<'py>( @@ -49,18 +50,31 @@ fn context_variable<'py>( .call((name,), Some(&keywords)) } +fn offered(py: Python<'_>) -> PyResult> { + Ok(OFFERED + .get_or_try_init(py, || { + context_variable(py, OFFERED_NAME, py.None().into_bound(py)).map(Bound::unbind) + })? + .bind(py) + .clone()) +} + +fn withheld(py: Python<'_>) -> PyResult> { + Ok(WITHHELD + .get_or_try_init(py, || { + context_variable( + py, + WITHHELD_NAME, + PyBool::new(py, false).to_owned().into_any(), + ) + .map(Bound::unbind) + })? + .bind(py) + .clone()) +} + pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { let py: Python<'_> = module.py(); - module.add( - OFFERED_ATTRIBUTE, - context_variable(py, OFFERED_NAME, py.None().into_bound(py))?, - )?; - module.add( - WITHHELD_ATTRIBUTE, - context_variable( - py, - WITHHELD_NAME, - PyBool::new(py, false).to_owned().into_any(), - )?, - ) + module.add(OFFERED_ATTRIBUTE, offered(py)?)?; + module.add(WITHHELD_ATTRIBUTE, withheld(py)?) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cli/observability.rs b/crates/sqlbuild-python/src/bindings/_helpers/cli/observability.rs index bfeebfe9b0..6632089e7d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cli/observability.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cli/observability.rs @@ -14,14 +14,19 @@ use sqlbuild_cli::dispatch::models::{ExportSummary, ExporterLoss}; use sqlbuild_core::event_export::constants::DEFAULT_EVENT_EXPORT_SHUTDOWN_TIMEOUT_SECONDS; 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::cli::progress::NativeProgressProjector; +use crate::bindings::_helpers::cli::progress::machine_output_mode; +use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported, keywords}; +use crate::bindings::_helpers::event_exporting::dispatcher::EventExporterDispatcher; +use crate::bindings::_helpers::event_exporting::scope::EventExporterCommandScope; +use crate::bindings::_helpers::event_exporting::scope::event_exporter_command_scope; 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::fields::field as native_field; -use crate::bindings::_helpers::records::ids::NativeAttr; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::result_output::terminal_index::TerminalEventIndex; +use crate::bindings::_helpers::result_output::terminal_index::terminal_event_index_scope; const OBSERVABILITY_LOGGER: &str = "sqlbuild.cli.observability"; const DEBUG_COMMAND: &str = "debug"; @@ -340,24 +345,30 @@ impl Observed { timeout.extract::()? }; let timeout: Bound<'_, PyAny> = timeout.into_bound_py_any(py)?; - let delivery: Bound<'_, PyAny> = call_with( - ®istry::native(py, NativeAttr::EventExporterDispatcher)?, - &[ - ("shutdown_timeout_seconds", timeout), - ( - "failure_callback", - wrap_pyfunction!(log_exporter_failure, py)?.into_any(), - ), - ( - "summary_callback", - wrap_pyfunction!(log_exporter_summary, py)?.into_any(), - ), - ], - )?; - let scope: Bound<'_, PyAny> = call_with( - ®istry::native(py, NativeAttr::EventExporterCommandScope)?, - &[("dispatcher", delivery.clone())], - )?; + let delivery: Bound<'_, PyAny> = EventExporterDispatcher::new( + py, + Some(keywords( + py, + &[ + ("shutdown_timeout_seconds", timeout), + ( + "failure_callback", + wrap_pyfunction!(log_exporter_failure, py)?.into_any(), + ), + ( + "summary_callback", + wrap_pyfunction!(log_exporter_summary, py)?.into_any(), + ), + ], + )?), + ) + .and_then(|value| pyo3::Bound::new(py, value)) + .map(pyo3::Bound::into_any)?; + let scope: Bound<'_, PyAny> = pyo3::Bound::new( + py, + EventExporterCommandScope::new(delivery.clone().unbind()), + ) + .map(pyo3::Bound::into_any)?; self.exporter_scope = Some(scope.clone().unbind()); call_with( &native_field(&scope, "configure_extensions")?, @@ -378,10 +389,10 @@ impl Observed { self.enter( host::entry(py, ExecutorHost::DispatcherScope)?.call1((self.dispatcher.bind(py),))?, )?; - self.enter( - registry::native(py, NativeAttr::TerminalEventIndexScopeFunction)? - .call1((terminal_index,))?, - )?; + self.enter(pyo3::IntoPyObjectExt::into_bound_py_any( + terminal_event_index_scope(terminal_index.extract::>()?), + py, + )?)?; let Some(scope) = self .exporter_scope .as_ref() @@ -389,10 +400,10 @@ impl Observed { else { return Ok(()); }; - self.enter( - registry::native(py, NativeAttr::EventExporterCommandScopeFunction)? - .call1((&scope,))?, - )?; + self.enter(pyo3::IntoPyObjectExt::into_bound_py_any( + event_exporter_command_scope(scope.clone().unbind()), + py, + )?)?; let identity: Bound<'_, PyAny> = host::entry(py, ExecutorHost::CurrentExecutionIdentity)?.call0()?; if !identity.is_none() && command_output_sinks { @@ -439,8 +450,9 @@ impl CliObservabilityScope { wrap_pyfunction!(log_dispatch_failure, py)?.into_any(), )], )?; - let terminal_index: Bound<'_, PyAny> = - registry::native(py, NativeAttr::TerminalEventIndex)?.call0()?; + let terminal_index: Bound<'_, PyAny> = TerminalEventIndex::new(py) + .and_then(|value| pyo3::Bound::new(py, value)) + .map(pyo3::Bound::into_any)?; let subscribe = |subscriber: Bound<'_, PyAny>| -> PyResult> { Ok(call_with( &native_field(&dispatcher, "subscribe_lifecycle")?, @@ -456,8 +468,8 @@ impl CliObservabilityScope { }; let unsubscribe_terminal_index: Py = subscribe(native_field(&terminal_index, "consume")?)?; - let machine_output: bool = registry::native(py, NativeAttr::MachineOutputMode)? - .call1((args,))? + let machine_output: bool = machine_output_mode(args) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))? .is_truthy()?; let debug: Bound<'_, PyAny> = native_field(args, "debug")?; let progress_on_stderr: bool = machine_output @@ -466,24 +478,21 @@ impl CliObservabilityScope { let use_color: bool = !machine_output && !native_field(args, "no_color")?.is_truthy()? && stdout_supports_color(py)?; - let projector: Bound<'_, PyAny> = call_with( - ®istry::native(py, NativeAttr::NativeProgressProjector)?, - &[ - ( - "stream", - py.import("sys")?.getattr(if progress_on_stderr { - "stderr" - } else { - "stdout" - })?, - ), - ( - "use_color", - PyBool::new(py, use_color).to_owned().into_any(), - ), - ("debug", debug), - ], - )?; + let projector: Bound<'_, PyAny> = NativeProgressProjector::new( + py, + py.import("sys")?.getattr(if progress_on_stderr { + "stderr" + } else { + "stdout" + })?, + PyBool::new(py, use_color) + .to_owned() + .into_any() + .extract::()?, + debug.extract::()?, + ) + .and_then(|value| pyo3::Bound::new(py, value)) + .map(pyo3::Bound::into_any)?; let unsubscribe_progress: Py = subscribe(native_field(&projector, "consume")?)?; let projector_token: Bound<'_, PyAny> = projector.call_method0("install")?; let mut observed: Observed = Observed { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cli/progress.rs b/crates/sqlbuild-python/src/bindings/_helpers/cli/progress.rs index 5e9a2722f7..ab3cc794a2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cli/progress.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cli/progress.rs @@ -4,6 +4,7 @@ use std::sync::{Mutex, MutexGuard}; use pyo3::exceptions::PyRuntimeError; use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; +use pyo3::sync::PyOnceLock; use pyo3::types::{PyBool, PyDict, PyDictMethods, PyFloat, PyInt, PyMapping, PyString}; use pyo3::{pyclass, pyfunction, pymethods, wrap_pyfunction}; use sqlbuild_core::progress::main::claim_progress_terminal::claim_progress_terminal; @@ -16,14 +17,16 @@ use sqlbuild_core::progress::models::{ }; use sqlbuild_core::terminal_events::models::TerminalRequest; +use crate::bindings::_helpers::build_progress::transient_lines::shared_transient_line_coordinator; use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; use crate::bindings::_helpers::records::fields::field as native_field; -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"; + +/// The `ContextVar` holding the installed projector. +static CONTEXT: PyOnceLock> = PyOnceLock::new(); const CLAIM_OPTIONS: (&[&str], &[&str]) = ( &["resource_name"], &["resource_id", "resource_attempt_id", "attempt_number"], @@ -134,7 +137,8 @@ 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> { - registry::native(py, NativeAttr::SharedTransientLineCoordinator)?.call0() + shared_transient_line_coordinator(py) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py)) } fn persist(coordinator: &Bound<'_, PyAny>, stream: &Bound<'_, PyAny>, text: &str) -> PyResult<()> { @@ -214,7 +218,7 @@ fn terminal_request( impl NativeProgressProjector { #[new] #[pyo3(signature = (*, stream, use_color, debug = false))] - fn new( + pub(crate) fn new( py: Python<'_>, stream: Bound<'_, PyAny>, use_color: bool, @@ -310,7 +314,19 @@ impl NativeProgressProjector { } fn context_variable(py: Python<'_>) -> PyResult> { - registry::native(py, NativeAttr::NativeProgressProjectorContext) + Ok(CONTEXT + .get_or_try_init(py, || { + let keywords: Bound<'_, PyDict> = PyDict::new(py); + keywords.set_item("default", py.None())?; + Ok::<_, pyo3::PyErr>( + py.import("contextvars")? + .getattr("ContextVar")? + .call((CONTEXT_NAME,), Some(&keywords))? + .unbind(), + ) + })? + .bind(py) + .clone()) } /// Return the projector installed for the current CLI invocation. @@ -321,7 +337,7 @@ pub(crate) fn current_native_progress_projector(py: Python<'_>) -> PyResult) -> PyResult { +pub(crate) fn machine_output_mode(args: &Bound<'_, PyAny>) -> PyResult { let mut dbt_args: Vec = Vec::new(); for arg in native_field(args, "dbt_args")?.try_iter()? { dbt_args.push(arg?.str()?.extract()?); @@ -337,14 +353,7 @@ fn machine_output_mode(args: &Bound<'_, PyAny>) -> PyResult { pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { let py: Python<'_> = module.py(); - let keywords: Bound<'_, PyDict> = PyDict::new(py); - keywords.set_item("default", py.None())?; - module.add( - CONTEXT_ATTRIBUTE, - py.import("contextvars")? - .getattr("ContextVar")? - .call((CONTEXT_NAME,), Some(&keywords))?, - )?; + module.add(CONTEXT_ATTRIBUTE, context_variable(py)?)?; module.add_class::()?; module.add_function(wrap_pyfunction!(machine_output_mode, module)?)?; module.add_function(wrap_pyfunction!(current_native_progress_projector, module)?) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cli/progress_reporters.rs b/crates/sqlbuild-python/src/bindings/_helpers/cli/progress_reporters.rs index ac240e891f..036656306a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cli/progress_reporters.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cli/progress_reporters.rs @@ -218,7 +218,7 @@ pub(crate) struct DbtInitProgressReporter { impl DbtInitProgressReporter { #[new] #[pyo3(signature = (*, stream, use_color))] - fn new(stream: &Bound<'_, PyAny>, use_color: bool) -> PyResult { + pub(crate) fn new(stream: &Bound<'_, PyAny>, use_color: bool) -> PyResult { Ok(Self { stream: stream.clone().unbind(), status: status_reporter(stream, use_color)?.unbind(), 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 4be88b9e0e..b37ffffe1f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_command.rs @@ -18,6 +18,7 @@ use sqlbuild_core::clone_text::main::validate_clone_policy::validate_clone_polic use sqlbuild_core::clone_text::main::validate_clone_target_names::validate_clone_target_names; use sqlbuild_core::clone_text::models::CloneItemText; +use crate::bindings::_helpers::cli::progress_reporters::PlanningProgressReporter; use crate::bindings::_helpers::clone::clone_execution::{ destination_entry_count, execute_clone_plan, resource_types_by_name, }; @@ -38,7 +39,7 @@ use crate::bindings::_helpers::planner_selection::unit_tests::reject_unit_test_s use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::result_output::documents::format_clone_execution_json; use crate::bindings::_helpers::result_output::documents::write_execution_json_output; @@ -87,10 +88,11 @@ fn progress_reporter<'py>( style: CliStyle, ) -> PyResult> { let py: Python<'py> = stream.py(); - let options: Bound<'py, PyDict> = PyDict::new(py); - options.set_item("stream", stream)?; - options.set_item("use_color", style.use_color)?; - registry::native(py, NativeAttr::PlanningProgressReporter)?.call((), Some(&options)) + Ok(Bound::new( + py, + PlanningProgressReporter::new(py, stream, style.use_color)?, + )? + .into_any()) } fn lifecycle<'py>(py: Python<'py>, (kind, name): (&str, &str)) -> PyResult> { @@ -316,7 +318,7 @@ fn clone<'py>( /// Run one `sqb clone` request. #[pyfunction] #[pyo3(signature = (*, request))] -fn run_clone_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { +pub(crate) fn run_clone_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { let invocation: Invocation<'_> = invocation(py, request)?; let (config, connection) = connect(request, &invocation)?; let outcome = clone(request, &invocation, (&config, &connection)); 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 8ca2f90f05..5ef10cec31 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_execution.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_execution.rs @@ -18,8 +18,8 @@ use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::records::construct::{enum_member, new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::result_output::event_writer::ExecutionEventWriter; const DESTINATION_ENTRY_GROUPS: [&str; 4] = [ "destination_source_entries", @@ -324,10 +324,12 @@ pub(crate) fn execute_clone_plan<'py>( ) -> PyResult<(Bound<'py, PyAny>, f64)> { let py: Python<'py> = pipeline.py(); let started: Instant = Instant::now(); - let event_writer: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::ExecutionEventWriter)?, - &[("path", native_field(request, "event_output_path")?)], - )?; + let event_writer: Bound<'py, PyAny> = ExecutionEventWriter::new( + py, + Some(native_field(request, "event_output_path")?).filter(|value| !value.is_none()), + ) + .and_then(|value| pyo3::Bound::new(py, value)) + .map(pyo3::Bound::into_any)?; let printer: Py = Py::new( py, CloneItemPrinter { 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 cfe0bcd61d..36e5c150f1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_pipeline.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_pipeline.rs @@ -148,7 +148,7 @@ pub(crate) fn prepared_clone_pipeline<'py>( /// Run a clone pipeline with explicit compilation controls. #[pyfunction] #[pyo3(signature = (**request))] -fn run_clone_pipeline_with_options<'py>( +pub(crate) fn run_clone_pipeline_with_options<'py>( request: Option<&Bound<'py, PyDict>>, ) -> PyResult> { let request: Request<'_, 'py> = Request::new("run_clone_pipeline_with_options", request)?; 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 f6fa2e6d13..e14811cbdd 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/invocation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/invocation.rs @@ -3,6 +3,8 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::PyBool; +use crate::bindings::_helpers::cli::progress_reporters::ConnectionProgressReporter; +use crate::bindings::_helpers::cli::progress_reporters::PlanningProgressReporter; use crate::bindings::_helpers::command_runtime::adapters::resolve_cli_adapter; use crate::bindings::_helpers::command_runtime::connection::resolve_project_connection_config; use crate::bindings::_helpers::compile_driver::discovery::discover_project_inputs; @@ -12,8 +14,7 @@ use crate::bindings::_helpers::contracts::spec_resolution::resolve_effective_ada use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::ids::RecordId; /// The project directory, discovered inputs, adapter context, colour and progress stream. type InvocationContext<'py> = ( @@ -142,27 +143,32 @@ pub(crate) fn resolve_reported_command_invocation<'py>( let mut fields: Vec<(&str, Bound<'py, PyAny>)> = context_fields(&context)?; fields.push(( "connection_progress", - call_with( - ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, - &[ - ( - "adapter_name", - native_field(adapter_context, "adapter_name")?, - ), - ("stream", stream.clone()), - ("use_color", boolean(py, *use_color)), - ], - )?, + ConnectionProgressReporter::new( + py, + Some(keywords( + py, + &[ + ( + "adapter_name", + native_field(adapter_context, "adapter_name")?, + ), + ("stream", stream.clone()), + ("use_color", boolean(py, *use_color)), + ], + )?), + ) + .and_then(|value| pyo3::Bound::new(py, value)) + .map(pyo3::Bound::into_any)?, )); fields.push(( "planning_progress", - call_with( - ®istry::native(py, NativeAttr::PlanningProgressReporter)?, - &[ - ("stream", stream.clone()), - ("use_color", boolean(py, *use_color)), - ], - )?, + PlanningProgressReporter::new( + py, + &stream.clone(), + boolean(py, *use_color).extract::()?, + ) + .and_then(|value| pyo3::Bound::new(py, value)) + .map(pyo3::Bound::into_any)?, )); call_with(invocation_type, &fields) } 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 a6e3b7eac9..7b9a63b922 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 @@ -4,20 +4,35 @@ use std::sync::Mutex; use pyo3::exceptions::PyRuntimeError; use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; +use pyo3::sync::PyOnceLock; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; use pyo3::{pyclass, pyfunction, pymethods, wrap_pyfunction}; use sqlbuild_config::command_connection::constants::COMMAND_WAREHOUSE_GROUPS; use crate::bindings::_helpers::records::construct::{enum_from_value, new_record_with}; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::ids::RecordId; const CONTEXT_ATTRIBUTE: &str = "_command_warehouse_scope_context"; const CONTEXT_NAME: &str = "sqlbuild_command_warehouse_scope"; +/// The `ContextVar` holding the active command's warehouse scope. +static CONTEXT: PyOnceLock> = PyOnceLock::new(); + fn context_variable(py: Python<'_>) -> PyResult> { - registry::native(py, NativeAttr::CommandWarehouseScopeContext) + Ok(CONTEXT + .get_or_try_init(py, || { + let keywords: Bound<'_, PyDict> = PyDict::new(py); + keywords.set_item("default", py.None())?; + Ok::<_, pyo3::PyErr>( + py.import("contextvars")? + .getattr("ContextVar")? + .call((CONTEXT_NAME,), Some(&keywords))? + .unbind(), + ) + })? + .bind(py) + .clone()) } /// Installs one command's warehouse scope for the duration of a `with` block. @@ -112,14 +127,7 @@ pub(crate) fn current_command_warehouse_scope(py: Python<'_>) -> PyResult) -> PyResult<()> { let py: Python<'_> = module.py(); - let keywords: Bound<'_, PyDict> = PyDict::new(py); - keywords.set_item("default", py.None())?; - module.add( - CONTEXT_ATTRIBUTE, - py.import("contextvars")? - .getattr("ContextVar")? - .call((CONTEXT_NAME,), Some(&keywords))?, - )?; + module.add(CONTEXT_ATTRIBUTE, context_variable(py)?)?; module.add_class::()?; module.add_function(wrap_pyfunction!(command_warehouse_scope, module)?)?; module.add_function(wrap_pyfunction!(current_command_warehouse_scope, module)?) 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 7564bc8522..ac7a4d5791 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 @@ -4,17 +4,20 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python}; use pyo3::pyfunction; use pyo3::types::PyTuple; +use crate::bindings::_helpers::cache::sql_test_scan_store::SqlTestScanCache; use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; use crate::bindings::_helpers::compile_driver::stage_work::{DECLARATION_SCOPES, count_stage}; use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::ids::{ErrorId, 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::authored_ctes::validate_authored_sql_test_ctes; use crate::bindings::_helpers::sql_test_glue::constants::SQL_TEST_EXPECTED_MODELS_SCAN_ALGORITHM; +use crate::bindings::_helpers::sqltext::lexical_syntax::LexicalSyntaxInput; fn compile_error(py: Python<'_>, message: Bound<'_, PyAny>) -> PyResult { Ok(PyErr::from_value( @@ -54,20 +57,24 @@ pub(crate) fn build_declaration_scope<'py>( ) -> PyResult> { let py: Python<'py> = discovered_inputs.py(); count_stage(py, DECLARATION_SCOPES, 1)?; - let scan_cache: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::SqlTestScanCache)?, - &[( - "cache_dir", - compile_cache_dir.unwrap_or_else(|| py.None().into_bound(py)), - )], - )?; + let scan_cache: Bound<'py, PyAny> = pyo3::Bound::new( + py, + SqlTestScanCache::new( + compile_cache_dir + .unwrap_or_else(|| py.None().into_bound(py)) + .extract::>()?, + ), + ) + .map(pyo3::Bound::into_any)?; let test_files: Bound<'py, PyAny> = native_field(discovered_inputs, "test_files")?; let scenario_files: Bound<'py, PyAny> = native_field(discovered_inputs, "scenario_files")?; - registry::native(py, NativeAttr::ValidateAuthoredSqlTestCtes)?.call1(( - &test_files, - &scenario_files, - native_field(sql_lexical_syntax, "native_mapping")?, - ))?; + validate_authored_sql_test_ctes( + py, + test_files.clone(), + scenario_files.clone(), + native_field(sql_lexical_syntax, "native_mapping")?.extract::()?, + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; let native: Bound<'py, PyAny> = Bound::new( py, open_native_scope_index(py, discovered_inputs, loaded_macros, None)?, 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 75fcf19281..73b307a1f3 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 @@ -2,11 +2,14 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyErr, PyResult, Python}; use pyo3::pyfunction; -use pyo3::types::{PyDict, PyList, PyTuple}; +use pyo3::types::{PyDict, PyString, PyTuple}; use sqlbuild_model_config::templates::models::TemplateOptions; use crate::bindings::_helpers::compile_driver::python_calls::imported; +use crate::bindings::_helpers::contracts::effective_config::effective_config_connection; +use crate::bindings::_helpers::contracts::effective_config::effective_config_settings; +use crate::bindings::_helpers::contracts::effective_config::effective_config_vars; use crate::bindings::_helpers::contracts::spec_resolution::{ resolve_target_config, resolve_target_name, }; @@ -15,7 +18,7 @@ use crate::bindings::_helpers::model_config::template_data::{ }; use crate::bindings::_helpers::records::construct::{new_record, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; const CONNECTION_LABEL: &str = "effective connection"; @@ -31,8 +34,8 @@ pub(crate) fn build_effective_connection<'py>( ) -> PyResult> { let py: Python<'py> = project_config.py(); let (connection, error): (Bound<'py, PyAny>, Option) = - registry::native(py, NativeAttr::EffectiveConfigConnection)? - .call1((project_config, local_config, target_config))? + effective_config_connection(py, project_config, local_config, target_config) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))? .extract()?; if connection.is_none() { let raised: Bound<'py, PyAny> = @@ -67,16 +70,20 @@ pub(crate) fn build_effective_settings<'py>( .try_iter()? .map(|field| native_field(&field?, "name")) .collect::>()?; - let values: Bound<'py, PyDict> = registry::native(py, NativeAttr::EffectiveConfigSettings)? - .call1(( - ( - native_field(project_config, "settings")?, - native_field(local_config, "settings")?, - ), - native_field(local_config, "setting_overrides")?, - PyList::new(py, names)?, - ))? - .downcast_into()?; + let values: Bound<'py, PyDict> = effective_config_settings( + py, + ( + native_field(project_config, "settings")?, + native_field(local_config, "settings")?, + ), + &native_field(local_config, "setting_overrides")?, + names + .into_iter() + .map(|name| Ok(name.cast_into::()?)) + .collect::>()?, + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))? + .downcast_into()?; new_record(py, RecordId::SettingsConfig, (), Some(&values)) } @@ -90,8 +97,13 @@ 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> = registry::native(py, NativeAttr::EffectiveConfigVars)? - .call1(((project_config, local_config), target_config, cli_vars))?; + let merged: Bound<'py, PyAny> = effective_config_vars( + py, + (project_config.clone(), local_config.clone()), + target_config, + cli_vars, + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; expanded_effective_vars(merged.downcast()?) } 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 dcc2f63095..0b909b6a94 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,6 +16,7 @@ use crate::bindings::_helpers::compiled_project::model_loop::NativeModelLoop; use crate::bindings::_helpers::compiled_project::project::NativeCompiledProject; use crate::bindings::_helpers::discovery::model_file_record::NativeModelFile; use crate::bindings::_helpers::model_config::contract_classes::contract_classes; +use crate::bindings::_helpers::model_config::model_validator::NativeModelValidator; use crate::bindings::_helpers::model_config::unicode_environment::unicode_environment; use crate::bindings::_helpers::records::construct::{ enum_member, new_record, new_record_with, record_class, @@ -26,7 +27,7 @@ use crate::bindings::_helpers::render::macro_expansion_result::macro_expansion_c use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::native_host::user_code::UserCodeHost; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; use sqlbuild_core::constants::PROJECT_CONFIG_FILENAME; /// The explicit reference call kinds and the `SqlResourceRefKind` member each resolves to. @@ -128,7 +129,7 @@ fn sql_hook_inputs<'py>( ("environment", unicode_environment(py)?), ( "render_project_var", - registry::native(py, NativeAttr::RenderProjectVarText)?, + pyo3::wrap_pyfunction!(crate::bindings::_helpers::authored_values::project_vars::render_project_var_text, py)?.into_any(), ), ( "sql_resource_ref", @@ -174,14 +175,14 @@ 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> = native_field(context, "effective_settings")?; - let validator: Bound<'py, PyAny> = registry::native(py, NativeAttr::NativeModelValidator)? - .call_method1( - "from_inputs", - ( - discovered_inputs, - native_field(&settings, "microbatch_concurrency")?, - ), - )?; + let validator: Bound<'py, PyAny> = Bound::new( + py, + NativeModelValidator::from_inputs( + discovered_inputs.clone(), + native_field(&settings, "microbatch_concurrency")?.extract()?, + )?, + )? + .into_any(); if !external.is_none() { call_with( &native_field(&external, "validate_model_names")?, @@ -227,11 +228,11 @@ pub(crate) fn build_model_inputs<'py>( vec![ ( "report_diagnostic", - registry::native(py, NativeAttr::ReportCompileDiagnostic)?, + pyo3::wrap_pyfunction!(crate::bindings::_helpers::compile_driver::diagnostic_collector::report_compile_diagnostic, py)?.into_any(), ), ( "record_template_reads", - registry::native(py, NativeAttr::RecordTemplateReads)?, + pyo3::wrap_pyfunction!(crate::bindings::_helpers::model_config::template_data::record_template_reads, py)?.into_any(), ), ( "visible_declarations", @@ -263,20 +264,26 @@ pub(crate) fn build_model_inputs<'py>( )?; let syntax: Bound<'py, PyAny> = native_field(context, "sql_lexical_syntax")?; let native_loop: Bound<'py, PyAny> = - registry::native(py, NativeAttr::NativeModelLoop)?.call1(( - (validator, contract_classes(py)?.into_any(), classes), - native_field(&syntax, "native_mapping")?, + crate::bindings::_helpers::compiled_project::model_loop::NativeModelLoop::new( + ( + validator.unbind(), + contract_classes(py)?.unbind(), + classes.unbind(), + ), + native_field(&syntax, "native_mapping")?.extract()?, reserved_cursor_markers(), ( ( - native_field(&settings, "sql_analysis")?, + native_field(&settings, "sql_analysis")?.extract()?, no_sql_validation, defer_model_sql_validation, - native_field(&settings, "require_sql_analysis")?, + native_field(&settings, "require_sql_analysis")?.extract()?, ), - project_config_path.str()?, + project_config_path.str()?.extract()?, ), - ))?; + ) + .and_then(|value| pyo3::Bound::new(py, value)) + .map(pyo3::Bound::into_any)?; let files: Vec> = discovered_inputs .getattr("model_files")? .try_iter()? 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 a78720c431..d29f6ee614 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 @@ -16,8 +16,8 @@ use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::native_host::user_code::UserCodeHost; use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::ids::RecordId; +use crate::bindings::_helpers::render::declaration_indexes::public_declaration_indexes; use pyo3::IntoPyObjectExt; /// Build the declaration scope of `discovered`; a static scope lends only its index to macros. @@ -196,8 +196,8 @@ pub(crate) fn sql_expansion_inputs(sources: ExpansionSources<'_>) -> PyResult, Bound<'_, PyAny>) = - registry::native(py, NativeAttr::PublicDeclarationIndexes)? - .call1((&discovered,))? + public_declaration_indexes(discovered.clone()) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))? .extract()?; let collection_rendering: Bound<'_, PyAny> = resolve_effective_collection_rendering(&project_config.clone(), &py.None().into_bound(py)) 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 10966ebdcc..dcf629121d 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 @@ -30,9 +30,11 @@ use crate::bindings::_helpers::compile_command::models::CompileReuseAttempt; use crate::bindings::_helpers::compile_command::runtime_identity::{ invocation_identity, redirected_output_files, runtime_identity, }; +use crate::bindings::_helpers::compile_driver::command::run_compile_command; 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::compile_outputs::reuse::{ReuseCheckPy, check_compile_reuse}; 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; @@ -382,8 +384,11 @@ fn attempt_compile_reuse<'py>(request: &Bound<'py, PyAny>) -> PyResult = registry::native(py, NativeAttr::CheckCompileReuse)? - .call1((reuse_check(request, &project_dir)?,))?; + let native: Bound<'py, PyAny> = check_compile_reuse( + py, + reuse_check(request, &project_dir)?.extract::()?, + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; let checked: String = native.call_method0("outcome")?.extract()?; let json_output: Bound<'py, PyAny> = native_field(request, "json_output")?; let mut base: Vec<(&'static str, Bound<'py, PyAny>)> = vec![ @@ -474,14 +479,19 @@ fn reuse_request<'py>(request: &Bound<'py, PyAny>) -> PyResult /// Run one compile command request, replaying the previous compile when nothing changed. #[pyfunction] -fn run_compile<'py>(request: &Bound<'py, PyAny>) -> PyResult> { +pub(crate) fn run_compile<'py>(request: &Bound<'py, PyAny>) -> PyResult> { let py: Python<'py> = request.py(); let attempt: Bound<'py, PyAny> = attempt_compile_reuse(&reuse_request(request)?)?; let exit_code: Bound<'py, PyAny> = native_field(&attempt, "exit_code")?; if native_field(&attempt, "outcome")?.eq(outcome(py, HIT)?)? && !exit_code.is_none() { return Ok(exit_code); } - registry::native(py, NativeAttr::RunCompileCommand)?.call1((request, attempt)) + run_compile_command( + py, + request.clone(), + Some(attempt).filter(|value| !value.is_none()), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py)) } pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { 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 654fd73631..0336f47434 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/command.rs @@ -54,7 +54,7 @@ struct Recorders<'py> { /// Run one full compile, recording it for reuse; returns the exit code. #[pyfunction] #[pyo3(signature = (request, reuse_attempt=None))] -fn run_compile_command<'py>( +pub(crate) fn run_compile_command<'py>( py: Python<'py>, request: Bound<'py, PyAny>, reuse_attempt: Option>, 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 e595a73320..7587ca6ddc 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/diff.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/diff.rs @@ -181,7 +181,7 @@ fn diff_model_names<'py>( /// Compile both environments and resolve selected diffable model names. #[pyfunction] #[pyo3(signature = (**options))] -fn run_diff_pipeline<'py>( +pub(crate) fn run_diff_pipeline<'py>( py: Python<'py>, options: Option>, ) -> PyResult> { 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 87ec2c5cbf..f2b67b6c55 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/project.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/project.rs @@ -22,7 +22,9 @@ use crate::bindings::_helpers::records::construct::{enum_member, new_record_with use crate::bindings::_helpers::compile_driver::assembly::assemble_project; use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::compile_driver::selection_phases::resolve_analysis_model_names; -use crate::bindings::_helpers::compile_driver::stage_work::{PROMOTION_CONFLICTS, count_stage}; +use crate::bindings::_helpers::compile_driver::stage_work::{ + PROJECT_GRAPHS, PROMOTION_CONFLICTS, count_stage, +}; use crate::bindings::_helpers::compiled_project::records::project_with; use crate::bindings::_helpers::contracts::contract_diagnostics::promotion_conflict_diagnostics; use crate::bindings::_helpers::contracts::spec_resolution::resolve_effective_adapter_name; @@ -322,6 +324,7 @@ pub(crate) fn build_project_graph<'py>( py: Python<'py>, options: Option>, ) -> PyResult> { + count_stage(py, PROJECT_GRAPHS, 1)?; let mut optional: Vec<&str> = GRAPH_OPTIONAL.to_vec(); optional.push("no_cache"); let arguments: KeywordArguments<'py> = KeywordArguments::read( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/stage_work.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/stage_work.rs index 2537090419..e57cb18e64 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/stage_work.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/stage_work.rs @@ -20,6 +20,9 @@ pub(crate) const PIVOT_PROOFS: &str = "pivot_proofs"; pub(crate) const SESSION_PIVOT_PROOFS: &str = "session_pivot_proofs"; pub(crate) const DECLARATION_SCOPES: &str = "build_declaration_scope"; pub(crate) const LINEAGE_GRAPH_BUILDS: &str = "lineage_graph_builds"; +pub(crate) const PROJECT_GRAPHS: &str = "build_project_graph"; +pub(crate) const FORMAT_LINT_BATCHES: &str = "format_lint_batches"; +pub(crate) const FORMAT_LINT_FILES: &str = "format_lint_files"; /// Compile stage runs since the last reset, by stage. #[pyclass] diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_outputs/reuse.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_outputs/reuse.rs index 2eec1a8b5b..ee68ca5a9e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_outputs/reuse.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_outputs/reuse.rs @@ -82,7 +82,7 @@ impl From for ReuseRules { /// One reuse check: where the project and its store slot are, and the host's identity facts. #[derive(FromPyObject)] #[pyo3(from_item_all)] -struct ReuseCheckPy { +pub(crate) struct ReuseCheckPy { project_dir: PathBuf, store_directory: PathBuf, selected_target: Option, @@ -115,7 +115,7 @@ struct CompileRecordPy { /// One invocation's native reuse state, from the check to the record. #[pyclass(module = "sqlbuild._native", frozen)] -struct NativeReuseAttempt { +pub(crate) struct NativeReuseAttempt { attempt: Mutex, } @@ -246,7 +246,10 @@ fn recorded_artifact(artifact: ArtifactRow) -> RecordedArtifact { /// Check the stored compile for this invocation, replaying it on a hit. #[pyfunction] -fn check_compile_reuse(py: Python<'_>, check: ReuseCheckPy) -> PyResult { +pub(crate) fn check_compile_reuse( + py: Python<'_>, + check: ReuseCheckPy, +) -> PyResult { compiler_guard(|| { let rules: ReuseRules = check.rules.into(); let request: ReuseCheck<'_> = ReuseCheck { 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 0128c4784a..8fa6c0ca60 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 @@ -9,7 +9,7 @@ use crate::bindings::_helpers::native_host::facade::FacadeHost; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::records::construct::{enum_member, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; /// The materializations a model's storage policies are inherited for, besides none. @@ -393,7 +393,11 @@ pub(crate) fn model_loop_classes<'py>( ("replace", imported(py, "dataclasses", "replace")?), ( "declaration_rows", - registry::native(py, NativeAttr::DeclarationRows)?, + pyo3::wrap_pyfunction!( + crate::bindings::_helpers::compiled_project::records::declaration_rows, + py + )? + .into_any(), ), ], )?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop.rs index 92bc8e9392..c99828098e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop.rs @@ -201,7 +201,7 @@ struct LoopModel<'a, 'py> { #[pymethods] impl NativeModelLoop { #[new] - fn new( + pub(crate) fn new( checks: LoopChecks, syntax: LexicalSyntaxInput, reserved_markers: Vec, 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 1d584b3daf..45e55f2f77 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/contract_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/contract_command/command.rs @@ -19,6 +19,7 @@ use sqlbuild_core::contract_command::models::{ }; use crate::bindings::_helpers::build_progress::transient_status::status_reporter; +use crate::bindings::_helpers::cli::progress_reporters::ConnectionProgressReporter; use crate::bindings::_helpers::command_runtime::invocation::resolve_adapter_connection_context; use crate::bindings::_helpers::compile_driver::discovery::discover_project_inputs; use crate::bindings::_helpers::compile_driver::pipeline_entries::build_project_graph_from_compiled_project; @@ -31,7 +32,7 @@ use crate::bindings::_helpers::planner_selection::unit_tests::reject_unit_test_s use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::refactor::contract_compare::compare_contracts; @@ -173,17 +174,22 @@ fn compare<'py>( ) -> PyResult> { let py: Python<'py> = request.py(); let adapter: Bound<'py, PyAny> = native_field(context, "adapter")?; - let reporter: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, - &[ - ("adapter_name", native_field(context, "adapter_name")?), - ("stream", progress.stream.clone()), - ( - "use_color", - PyBool::new(py, progress.use_color).to_owned().into_any(), - ), - ], - )?; + let reporter: Bound<'py, PyAny> = ConnectionProgressReporter::new( + py, + Some(keywords( + py, + &[ + ("adapter_name", native_field(context, "adapter_name")?), + ("stream", progress.stream.clone()), + ( + "use_color", + PyBool::new(py, progress.use_color).to_owned().into_any(), + ), + ], + )?), + ) + .and_then(|value| pyo3::Bound::new(py, value)) + .map(pyo3::Bound::into_any)?; let connection_started: Instant = Instant::now(); reporter.call_method1("on_connection_start", (1,))?; let connection: Bound<'py, PyAny> = @@ -361,7 +367,7 @@ fn progress_for<'py>(request: &Bound<'py, PyAny>) -> PyResult> { /// Compare or adopt selected contracts from one read-only target. #[pyfunction] #[pyo3(signature = (*, request))] -fn run_contract_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { +pub(crate) fn run_contract_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { let action: String = native_field(request, "action")?.str()?.to_string(); let generate_write: bool = action == GENERATE_ACTION && native_field(request, "write")?.is_truthy()?; 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 94c63346a4..1d66d0b819 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/contract_command/write.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/contract_command/write.rs @@ -6,12 +6,12 @@ use pyo3::types::{PyDict, PyDictMethods, PyList, PyListMethods, PyTuple}; use crate::bindings::_helpers::compile_driver::discovery::discover_project_inputs; use crate::bindings::_helpers::compile_driver::project::build_compiled_project; +use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::compile_driver::python_calls::keywords; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::format_command::files::write_file_atomically; use crate::bindings::_helpers::records::construct::{enum_member, new_record}; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::refactor::contract_edits::edit_contract_model; use crate::bindings::_helpers::refactor::contract_edits::edit_contract_source; use crate::bindings::_helpers::refactor::contract_edits::remaining_contract_findings; @@ -168,10 +168,8 @@ 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> = registry::native(py, NativeAttr::WriteAtomically)?; for (path, contents) in contents_by_path.iter() { - call_with(&write_atomically, &[("path", path), ("contents", contents)])?; + write_file_atomically(path.extract()?, &contents.extract::()?)?; } Ok(()) } 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 190a4f9be6..1a4a8ff37c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/contracts/contract_diagnostics.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/contracts/contract_diagnostics.rs @@ -8,13 +8,13 @@ use pyo3::{IntoPyObject, pyfunction, wrap_pyfunction}; use sqlbuild_sqltext::compiler::main::model_output_columns::model_output_columns; use crate::bindings::_helpers::compiled_project::project::NativeCompiledProject; +use crate::bindings::_helpers::compiled_project::records::compiled_project_facts; use crate::bindings::_helpers::contracts::model_contracts::{ ConflictRow, DiagnosticRow, evaluate_project_model_contracts, project_promotion_conflicts, }; use crate::bindings::_helpers::records::construct::{enum_from_value, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::sqltext::model_headers::{ColumnLocation, location_rows}; const PATHLIB_MODULE: &str = "pathlib"; @@ -37,7 +37,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> { - registry::native(project.py(), NativeAttr::CompiledProjectFacts)?.call1((project,)) + Ok(compiled_project_facts(project)?.into_any()) } /// Where the model's SQL produces `column_name`, recovered from the authored SQL when needed. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/contracts/effective_config.rs b/crates/sqlbuild-python/src/bindings/_helpers/contracts/effective_config.rs index 72fff61518..b325a031dc 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/contracts/effective_config.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/contracts/effective_config.rs @@ -255,7 +255,7 @@ pub(crate) fn effective_config_target_fields<'py>( /// `build_effective_vars` before template expansion. #[pyfunction] -fn effective_config_vars<'py>( +pub(crate) fn effective_config_vars<'py>( py: Python<'py>, configs: (Bound<'py, PyAny>, Bound<'py, PyAny>), target_config: &Bound<'py, PyAny>, @@ -283,7 +283,7 @@ fn named<'py>(mapping: &Bound<'py, PyAny>) -> PyResult, Pairs<' /// `build_effective_connection` before template expansion, or the unknown-connection message. #[pyfunction] -fn effective_config_connection<'py>( +pub(crate) fn effective_config_connection<'py>( py: Python<'py>, project_config: &Bound<'py, PyAny>, local_config: &Bound<'py, PyAny>, @@ -312,7 +312,7 @@ fn effective_config_connection<'py>( /// `build_effective_settings`: setting values by field name, local overrides applied. #[pyfunction] -fn effective_config_settings<'py>( +pub(crate) fn effective_config_settings<'py>( py: Python<'py>, settings: (Bound<'py, PyAny>, Bound<'py, PyAny>), overrides: &Bound<'py, PyAny>, 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 c650ce4d99..653f9281bf 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_command/command.rs @@ -17,14 +17,14 @@ use crate::bindings::_helpers::cost_command::bounds::{parse_bound, parse_since}; 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::cost_report::functions::format_cost_breakdown; use crate::bindings::_helpers::cost_report::functions::format_cost_history; use crate::bindings::_helpers::cost_report::functions::format_cost_history_json; +use crate::bindings::_helpers::cost_report::functions::format_cost_json; use crate::bindings::_helpers::cost_report::functions::list_cost_runs; use crate::bindings::_helpers::cost_report::functions::resolve_cost_run; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::NativeAttr; -use crate::bindings::_helpers::records::registry; /// The request fields every cost view reads. struct CostRequest<'py> { @@ -285,16 +285,16 @@ fn run_detail(request: &CostRequest<'_>) -> PyResult { } None => record.clone(), }; - let payload: String = registry::native(py, NativeAttr::FormatCostJson)? - .call1((json_record,))? + let payload: String = format_cost_json(&json_record) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))? .extract()?; request.emit(&payload, || { let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("record", &record)?; options.set_item("use_color", request.use_color()?)?; options.set_item("limit", limit)?; - registry::native(py, NativeAttr::FormatCostBreakdown)? - .call((), Some(&options))? + format_cost_breakdown(py, Some(options.clone())) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))? .extract() })?; Ok(0) @@ -311,7 +311,7 @@ fn report(request: &Bound<'_, PyAny>) -> PyResult { /// Refresh a delayed run, then render persisted cost history or one run breakdown. #[pyfunction] -fn run_cost_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { +pub(crate) fn run_cost_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { refresh_pending_cost_run(py, request); report(request) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cost_report/functions.rs b/crates/sqlbuild-python/src/bindings/_helpers/cost_report/functions.rs index 973cb5808d..f1288d7a3e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_report/functions.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_report/functions.rs @@ -80,7 +80,7 @@ pub(crate) fn resolve_cost_run<'py>( /// `sqb cost --json` text of one run. #[pyfunction] -fn format_cost_json(record: &Bound<'_, PyAny>) -> PyResult { +pub(crate) fn format_cost_json(record: &Bound<'_, PyAny>) -> PyResult { Ok(cost_report_json(&cost_run(record)?)) } @@ -107,7 +107,10 @@ pub(crate) fn cost_output_payload( /// The aligned human breakdown of one run. #[pyfunction] #[pyo3(signature = (**options))] -fn format_cost_breakdown(py: Python<'_>, options: Option>) -> PyResult { +pub(crate) fn format_cost_breakdown( + py: Python<'_>, + options: Option>, +) -> PyResult { let arguments: KeywordArguments<'_> = KeywordArguments::read(py, "format_cost_breakdown", options, BREAKDOWN_OPTIONS)?; let limit: Bound<'_, PyAny> = arguments.value("limit")?; 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 42aede93ad..786f5718cb 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/context.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/context.rs @@ -92,7 +92,7 @@ impl CostContext { #[staticmethod] #[pyo3(signature = (**options))] - fn scope(py: Python<'_>, options: Option>) -> PyResult { + pub(crate) fn scope(py: Python<'_>, options: Option>) -> PyResult { let arguments: KeywordArguments<'_> = KeywordArguments::read( py, "scope", 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 19e2ab6958..a6794f07de 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,13 @@ 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; +use crate::bindings::_helpers::cli::progress_reporters::DbtInitProgressReporter; use crate::bindings::_helpers::dbt_command::init_project::build_dbt_init_project; use crate::bindings::_helpers::dbt_command::support::{color_enabled, flag, path, stream}; use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::ids::RecordId; use sqlbuild_core::constants::PROJECT_CONFIG_FILENAME; fn project_config_found(directory: &Bound<'_, PyAny>) -> PyResult { @@ -82,13 +81,12 @@ pub(crate) fn ensure_twin_project<'py>( let use_color: bool = !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( - ®istry::native(py, NativeAttr::DbtInitProgressReporter)?, - &[ - ("stream", progress_stream.clone()), - ("use_color", flag(py, use_color)), - ], - )?; + let progress: Bound<'py, PyAny> = DbtInitProgressReporter::new( + &progress_stream.clone(), + flag(py, use_color).extract::()?, + ) + .and_then(|value| pyo3::Bound::new(py, value)) + .map(pyo3::Bound::into_any)?; let callbacks: Bound<'py, PyAny> = new_record_with( py, RecordId::DbtInitProgressCallbacks, 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 3a97405419..c015fa7d63 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/command.rs @@ -6,6 +6,7 @@ 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::cli::progress_reporters::PlanningProgressReporter; use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, keywords, }; @@ -41,13 +42,13 @@ impl<'py> Progress<'py> { "stdout" }, )?; - let reporter: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::PlanningProgressReporter)?, - &[ - ("stream", stream.clone()), - ("use_color", flag(py, use_color)), - ], - )?; + let reporter: Bound<'py, PyAny> = PlanningProgressReporter::new( + py, + &stream.clone(), + flag(py, use_color).extract::()?, + ) + .and_then(|value| pyo3::Bound::new(py, value)) + .map(pyo3::Bound::into_any)?; Ok(Self { stream, use_color, @@ -150,7 +151,7 @@ fn run_execution( /// Execute one `sqb dbt` interop command. #[pyfunction] #[pyo3(signature = (**options))] -fn run_dbt_command(py: Python<'_>, options: Option>) -> PyResult { +pub(crate) fn run_dbt_command(py: Python<'_>, options: Option>) -> PyResult { let arguments: KeywordArguments<'_> = KeywordArguments::read( py, "run_dbt_command", 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 5be9f9b1a7..c295f1379c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/init.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/init.rs @@ -11,13 +11,14 @@ use sqlbuild_core::dbt_command::main::render_dbt_init_preview::render_dbt_init_p use sqlbuild_core::dbt_command::main::render_dbt_init_summary::render_dbt_init_summary; use sqlbuild_core::dbt_command::models::DbtInitSummary; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, imported}; +use crate::bindings::_helpers::cli::progress_reporters::DbtInitProgressReporter; +use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::dbt_command::init_project::build_dbt_init_project; use crate::bindings::_helpers::dbt_command::init_project::validate_dbt_init_project; use crate::bindings::_helpers::dbt_command::support::{color_enabled, flag, path, stream}; use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; fn missing_project(py: Python<'_>) -> PyErr { @@ -80,16 +81,15 @@ fn summary(result: &Bound<'_, PyAny>) -> PyResult { /// Execute SQLBuild-owned `sqb dbt init`. #[pyfunction] #[pyo3(signature = (request))] -fn run_dbt_init_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { +pub(crate) 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( - ®istry::native(py, NativeAttr::DbtInitProgressReporter)?, - &[ - ("stream", stream(py, "stdout")?), - ("use_color", flag(py, use_color)), - ], - )?; + let progress: Bound<'_, PyAny> = DbtInitProgressReporter::new( + &stream(py, "stdout")?, + flag(py, use_color).extract::()?, + ) + .and_then(|value| pyo3::Bound::new(py, value)) + .map(pyo3::Bound::into_any)?; let callbacks: Bound<'_, PyAny> = new_record_with( py, RecordId::DbtInitProgressCallbacks, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dbt_events/stream.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_events/stream.rs index 75a10144dc..c1c472ddc7 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_events/stream.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_events/stream.rs @@ -88,7 +88,7 @@ impl DbtEventStream { impl DbtEventStream { #[new] #[pyo3(signature = (**options))] - fn new(py: Python<'_>, options: Option>) -> PyResult { + pub(crate) fn new(py: Python<'_>, options: Option>) -> PyResult { let arguments: KeywordArguments<'_> = KeywordArguments::read(py, "DbtEventStream", options, STREAM_OPTIONS)?; Ok(Self { 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 b005bba6bb..3360637d7d 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 @@ -24,12 +24,11 @@ use sqlbuild_core::dbt_process::models::DbtJsonProcess; use crate::bindings::_helpers::build_progress::transient_status::status_reporter; use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported, within}; +use crate::bindings::_helpers::dbt_events::stream::DbtEventStream; use crate::bindings::_helpers::dbt_interop::runner::{ dbt_command_lifecycle, launch_failure, launch_os_error, report_exit, }; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::NativeAttr; -use crate::bindings::_helpers::records::registry; use crate::bindings::types::CompilerDetach; const STREAM_OPTIONS: (&[&str], &[&str]) = ( @@ -98,7 +97,9 @@ pub(crate) fn execute_dbt_json_event_stream<'py>( arguments.value("detail_by_unique_id")?, )?; keywords.set_item("use_color", output.use_color)?; - registry::native(py, NativeAttr::DbtEventStream)?.call((), Some(&keywords))? + DbtEventStream::new(py, Some(keywords.clone())) + .and_then(|value| pyo3::Bound::new(py, value)) + .map(pyo3::Bound::into_any)? }; 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/execution.rs b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/execution.rs index a7e5c92a32..4d4e9d7df4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/execution.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/execution.rs @@ -25,7 +25,7 @@ const MERGED_OPTIONS: (&[&str], &[&str]) = (&["command", "options", "routed_args /// Merge the original dbt selection with dbt models SQLBuild work requires; `None` without dbt work. #[pyfunction] #[pyo3(signature = (**options))] -fn build_merged_dbt_execution_argv<'py>( +pub(crate) fn build_merged_dbt_execution_argv<'py>( py: Python<'py>, options: Option>, ) -> PyResult>> { @@ -80,7 +80,7 @@ fn executed_nodes(node_results: &Bound<'_, PyAny>) -> PyResult( +pub(crate) fn build_failed_sqlbuild_model_names<'py>( graph: &Bound<'py, DbtCombinedGraph>, manifest: &Bound<'py, PyAny>, node_results: &Bound<'py, PyAny>, 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 4b569c2c21..040dab6785 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/graph.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/graph.rs @@ -19,11 +19,14 @@ use sqlbuild_core::dbt_graph::models::{ }; use sqlbuild_core::dbt_graph::types::DbtSelectorResolver; -use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; use crate::bindings::_helpers::dbt_interop::manifest::resolve_sqlbuild_model_dbt_refs; +use crate::bindings::_helpers::planner_selection::model_selectors::{ + resolve_sqlbuild_model_selector_names, split_selector_expansion, +}; use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; const MODEL_RESOURCE_TYPE: &str = "model"; @@ -178,25 +181,19 @@ pub(crate) fn build_dbt_combined_graph<'py>( pub(crate) struct PlannerResolver<'py> { project: Bound<'py, PyAny>, - split: Bound<'py, PyAny>, - resolve: Bound<'py, PyAny>, } /// The planner's selector parsing and SQLBuild model selector resolution over `project`. pub(crate) fn planner_resolver(project: Bound<'_, PyAny>) -> PyResult> { - let py: Python<'_> = project.py(); - Ok(PlannerResolver { - split: registry::native(py, NativeAttr::SplitSelectorExpansion)?, - resolve: registry::native(py, NativeAttr::ResolveSqlbuildModelSelectorNames)?, - project, - }) + Ok(PlannerResolver { project }) } impl DbtSelectorResolver for PlannerResolver<'_> { type Error = PyErr; fn split(&self, term: &str) -> PyResult { - let parsed: Bound<'_, PyAny> = self.split.call1((term,))?; + let parsed: Bound<'_, PyAny> = + split_selector_expansion(&PyString::new(self.project.py(), term))?; Ok(DbtSelectorExpansion { core: native_field(&parsed, "core")?.extract()?, upstream: native_field(&parsed, "upstream")?.is_truthy()?, @@ -206,14 +203,9 @@ impl DbtSelectorResolver for PlannerResolver<'_> { fn model_names(&self, core: &str) -> PyResult<(Vec, Option)> { let py: Python<'_> = self.project.py(); - let (names, translated): (Bound<'_, PyAny>, Option) = call_with( - &self.resolve, - &[ - ("project", self.project.clone()), - ("term", PyString::new(py, core).into_any()), - ], - )? - .extract()?; + let (names, translated): (Bound<'_, PyAny>, Option) = + resolve_sqlbuild_model_selector_names(&self.project, &PyString::new(py, core))? + .extract()?; Ok((texts(&names)?, translated)) } } 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 bb88d473fc..ccf2edf5de 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/pipeline.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/pipeline.rs @@ -9,10 +9,18 @@ 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::cli::progress_reporters::ConnectionProgressReporter; use crate::bindings::_helpers::compile_driver::discovery::discover_project_inputs; +use crate::bindings::_helpers::compile_driver::pipeline_entries::plan_has_executable_work; use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; +use crate::bindings::_helpers::dbt_interop::cli::resolve_dbt_config; +use crate::bindings::_helpers::dbt_interop::execution::build_failed_sqlbuild_model_names; +use crate::bindings::_helpers::dbt_interop::execution::build_merged_dbt_execution_argv; +use crate::bindings::_helpers::dbt_interop::pipeline_phases::resolve_dbt_connection_config; +use crate::bindings::_helpers::dbt_interop::pipeline_phases::run_dbt_sqlbuild_work; use crate::bindings::_helpers::dbt_interop::pipeline_prologue::progress; +use crate::bindings::_helpers::dbt_interop::runner::DbtRunner; use crate::bindings::_helpers::dbt_interop::runtime_config::config_overrides; use crate::bindings::_helpers::records::construct::{enum_member, new_record_with, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; @@ -50,9 +58,8 @@ fn prepare<'py>( let py = request.py(); let invocation = registry::native(py, NativeAttr::ResolveDbtExecutionInvocation)?.call1((request,))?; - let manifest = native( - py, - NativeAttr::LoadCompiledDbtManifest, + let manifest = call_with( + ®istry::native(py, NativeAttr::LoadCompiledDbtManifest)?, &[ ("runner", native_field(&invocation, "runner")?), ("dbt_options", native_field(&invocation, "dbt_options")?), @@ -60,9 +67,8 @@ fn prepare<'py>( ("on_progress", native_field(request, "on_progress")?), ], )?; - let compiled = native( - py, - NativeAttr::CompileDbtInteropProject, + let compiled = call_with( + ®istry::native(py, NativeAttr::CompileDbtInteropProject)?, &[ ("project_dir", native_field(request, "project_dir")?), ( @@ -121,22 +127,24 @@ fn execute_dbt_interop_from_project(request: &Bound<'_, PyAny>) -> PyResult let (invocation, manifest, compiled) = prepare(request)?; let resolution = resolve_plan(request, &invocation, &compiled, &manifest)?; let plan = native_field(&resolution, "plan")?; - let merged = native( + let merged = build_merged_dbt_execution_argv( py, - NativeAttr::BuildMergedDbtExecutionArgv, - &[ - ("command", command), - ("options", native_field(&invocation, "dbt_options")?), - ( - "routed_args", - native_field(&invocation, "routed")?.getattr("dbt_args")?, - ), - ("plan", plan.clone()), - ], - )?; - native( - py, - NativeAttr::WriteDbtExecutionPlanText, + Some(keywords( + py, + &[ + ("command", command), + ("options", native_field(&invocation, "dbt_options")?), + ( + "routed_args", + native_field(&invocation, "routed")?.getattr("dbt_args")?, + ), + ("plan", plan.clone()), + ], + )?), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; + call_with( + ®istry::native(py, NativeAttr::WriteDbtExecutionPlanText)?, &[ ("request", request.clone()), ("invocation", invocation.clone()), @@ -144,40 +152,35 @@ fn execute_dbt_interop_from_project(request: &Bound<'_, PyAny>) -> PyResult ("merged_dbt_argv", merged.clone()), ], )?; - let execution = native( - py, - NativeAttr::ExecuteDbtWithoutStateTracking, + let execution = call_with( + ®istry::native(py, NativeAttr::ExecuteDbtWithoutStateTracking)?, &[ ("request", request.clone()), ("invocation", invocation.clone()), ("merged_dbt_argv", merged), ], )?; - native( - py, - NativeAttr::WriteDbtExecutionSummary, + call_with( + ®istry::native(py, NativeAttr::WriteDbtExecutionSummary)?, &[ ("request", request.clone()), ("invocation", invocation.clone()), ("execution", execution.clone()), ], )?; - let failed = native( - py, - NativeAttr::BuildFailedSqlbuildModelNames, - &[ - ("graph", native_field(&resolution, "graph")?), - ("manifest", manifest.clone()), - ("node_results", native_field(&execution, "node_results")?), - ], - )?; + let failed = build_failed_sqlbuild_model_names( + &native_field(&resolution, "graph")? + .cast_into::()?, + &manifest.clone(), + &native_field(&execution, "node_results")?, + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; let code: i64 = native_field(&execution, "returncode")?.extract()?; if code != 0 && !native_field(&execution, "node_results")?.is_truthy()? { return Ok(code); } - let output = native( - py, - NativeAttr::ResolveSqlbuildExecutionPlanOutput, + let output = call_with( + ®istry::native(py, NativeAttr::ResolveSqlbuildExecutionPlanOutput)?, &[ ("request", request.clone()), ("invocation", invocation.clone()), @@ -192,21 +195,17 @@ fn execute_dbt_interop_from_project(request: &Bound<'_, PyAny>) -> PyResult return Ok(code); } Some("No SQLBuild work selected.") - } else if !native( - py, - NativeAttr::PlanHasExecutableWork, - &[("plan", output.clone())], - )? - .is_truthy()? + } else if !plan_has_executable_work(py, Some(keywords(py, &[("plan", output.clone())])?)) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))? + .is_truthy()? { Some("Skipping SQLBuild: no executable work selected.") } else { None }; if let Some(message) = skip { - native( - py, - NativeAttr::WriteSqlbuildSkipNotice, + call_with( + ®istry::native(py, NativeAttr::WriteSqlbuildSkipNotice)?, &[ ("request", request.clone()), ("invocation", invocation), @@ -215,25 +214,29 @@ fn execute_dbt_interop_from_project(request: &Bound<'_, PyAny>) -> PyResult )?; return Ok(code); } - let connection = native( + let connection = resolve_dbt_connection_config( py, - NativeAttr::ResolveDbtConnectionConfig, - &[ - ("request", request.clone()), - ("invocation", invocation.clone()), - ("compiled", compiled.clone()), - ], + Some(keywords( + py, + &[ + ("request", request.clone()), + ("invocation", invocation.clone()), + ("compiled", compiled.clone()), + ], + )?), )?; - let result = native( + let result = run_dbt_sqlbuild_work( py, - NativeAttr::RunDbtSqlbuildWork, - &[ - ("request", request.clone()), - ("invocation", invocation), - ("compiled", compiled), - ("plan_output", output), - ("connection_config", connection), - ], + Some(keywords( + py, + &[ + ("request", request.clone()), + ("invocation", invocation), + ("compiled", compiled), + ("plan_output", output), + ("connection_config", connection), + ], + )?), )?; Ok(code.max(result.extract()?)) } @@ -280,21 +283,24 @@ pub(crate) fn plan_dbt_interop_from_project<'py>( let connection = if stream.is_none() { none(py) } else { - native( + ConnectionProgressReporter::new( py, - NativeAttr::ConnectionProgressReporter, - &[ - ("adapter_name", native_field(&compiled, "adapter_name")?), - ("stream", stream), - ("use_color", args.value_or_false("use_color")?), - ], - )? + Some(keywords( + py, + &[ + ("adapter_name", native_field(&compiled, "adapter_name")?), + ("stream", stream), + ("use_color", args.value_or_false("use_color")?), + ], + )?), + ) + .and_then(|value| pyo3::Bound::new(py, value)) + .map(pyo3::Bound::into_any)? }; let start = Instant::now(); let resolution = resolve_plan(&request, &invocation, &compiled, &manifest)?; - let plan = native( - py, - NativeAttr::AttachSqlbuildPlanOutput, + let plan = call_with( + ®istry::native(py, NativeAttr::AttachSqlbuildPlanOutput)?, &[ ("plan", native_field(&resolution, "plan")?), ("project_dir", args.value("project_dir")?), @@ -342,22 +348,24 @@ pub(crate) fn debug_dbt_from_project( .map(|arg| arg?.extract()) .collect::>()?; let overrides = config_overrides(py, &dbt_args)?; - let resolved = native( + let resolved = resolve_dbt_config( py, - NativeAttr::ResolveDbtConfig, - &[ - ("project_root", args.value("project_dir")?), - ( - "config", - native_field(&discovered, "project_config")?.getattr("dbt")?, - ), - ( - "local_config", - native_field(&discovered, "local_config")?.getattr("dbt")?, - ), - ("overrides", overrides), - ("require_project_dir", boolean(py, true)), - ], + Some(keywords( + py, + &[ + ("project_root", args.value("project_dir")?), + ( + "config", + native_field(&discovered, "project_config")?.getattr("dbt")?, + ), + ( + "local_config", + native_field(&discovered, "local_config")?.getattr("dbt")?, + ), + ("overrides", overrides), + ("require_project_dir", boolean(py, true)), + ], + )?), )?; let dbt_options = new_record_with( py, @@ -370,10 +378,14 @@ pub(crate) fn debug_dbt_from_project( )?; let mut runner = args.value("dbt_runner")?; if !runner.is_truthy()? { - runner = call_with( - ®istry::native(py, NativeAttr::DbtRunner)?, - &[("dbt_executable", args.value("dbt_executable")?)], - )?; + runner = pyo3::Bound::new( + py, + DbtRunner::new( + args.value("dbt_executable")?.extract::>()?, + None, + ), + ) + .map(pyo3::Bound::into_any)?; } let argv: Vec = args.value("args")?.extract()?; let result = call_with( 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 b602a7dc65..36a92e0dc3 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 @@ -5,8 +5,9 @@ use pyo3::types::PyDict; use pyo3::{pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::build_command::support::{boolean, emit, none, text, tuple}; +use crate::bindings::_helpers::cli::progress_reporters::ConnectionProgressReporter; use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, keywords, + KeywordArguments, imported, keywords, }; use crate::bindings::_helpers::contracts::spec_resolution::resolve_target_config; use crate::bindings::_helpers::dbt_interop::execution::render_dbt_execution_summary_footer; @@ -18,8 +19,7 @@ use crate::bindings::_helpers::dbt_interop::runtime_execution::execute_dbt_comma use crate::bindings::_helpers::dbt_interop::sqlbuild_work::execute_dbt_sqlbuild_work; use crate::bindings::_helpers::records::construct::{new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::ids::RecordId; const OPTIONS: (&[&str], &[&str]) = ( &["request", "invocation"], @@ -58,7 +58,7 @@ fn environment<'py>(args: &KeywordArguments<'py>) -> PyResult> /// Resolve the selected SQLBuild work's effective connection configuration. #[pyfunction] #[pyo3(signature = (**options))] -fn resolve_dbt_connection_config<'py>( +pub(crate) fn resolve_dbt_connection_config<'py>( py: Python<'py>, options: Option>, ) -> PyResult> { @@ -69,7 +69,7 @@ fn resolve_dbt_connection_config<'py>( /// Render the merged selection unless the command is producing JSON. #[pyfunction] #[pyo3(signature = (**options))] -fn write_dbt_execution_plan_text( +pub(crate) fn write_dbt_execution_plan_text( py: Python<'_>, options: Option>, ) -> PyResult<()> { @@ -128,7 +128,7 @@ fn write_dbt_execution_plan_text( /// Execute dbt without introducing any SQLBuild state callbacks. #[pyfunction] #[pyo3(signature = (**options))] -fn execute_dbt_without_state_tracking<'py>( +pub(crate) fn execute_dbt_without_state_tracking<'py>( py: Python<'py>, options: Option>, ) -> PyResult> { @@ -165,7 +165,10 @@ fn execute_dbt_without_state_tracking<'py>( /// Write the summary of actual dbt execution events. #[pyfunction] #[pyo3(signature = (**options))] -fn write_dbt_execution_summary(py: Python<'_>, options: Option>) -> PyResult<()> { +pub(crate) fn write_dbt_execution_summary( + py: Python<'_>, + options: Option>, +) -> PyResult<()> { let args = KeywordArguments::read(py, "write_dbt_execution_summary", options, OPTIONS)?; let footer = render_dbt_execution_summary_footer( &args.value("execution")?.getattr("node_results")?, @@ -186,7 +189,7 @@ fn write_dbt_execution_summary(py: Python<'_>, options: Option /// Plan SQLBuild work after dbt using actual dbt failures as external blockers. #[pyfunction] #[pyo3(signature = (**options))] -fn resolve_sqlbuild_execution_plan_output<'py>( +pub(crate) fn resolve_sqlbuild_execution_plan_output<'py>( py: Python<'py>, options: Option>, ) -> PyResult> { @@ -207,18 +210,23 @@ fn resolve_sqlbuild_execution_plan_output<'py>( let invocation = args.value("invocation")?; let stream = native_field(&invocation, "output_stream")?; emit(&stream, "\n")?; - let reporter = call_with( - ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, - &[ - ( - "adapter_name", - args.value("compiled")?.getattr("adapter_name")?, - ), - ("stream", stream), - ("blank_line_after_complete", boolean(py, true)), - ("use_color", args.value("request")?.getattr("use_color")?), - ], - )?; + let reporter = ConnectionProgressReporter::new( + py, + Some(keywords( + py, + &[ + ( + "adapter_name", + args.value("compiled")?.getattr("adapter_name")?, + ), + ("stream", stream), + ("blank_line_after_complete", boolean(py, true)), + ("use_color", args.value("request")?.getattr("use_color")?), + ], + )?), + ) + .and_then(|value| pyo3::Bound::new(py, value)) + .map(pyo3::Bound::into_any)?; let request = new_record_with( py, RecordId::DbtSqlbuildPlanRequest, @@ -267,7 +275,7 @@ fn execution_limits<'py>( /// Execute the planned SQLBuild phase with the selected target's execution limits. #[pyfunction] #[pyo3(signature = (**options))] -fn run_dbt_sqlbuild_work<'py>( +pub(crate) fn run_dbt_sqlbuild_work<'py>( py: Python<'py>, options: Option>, ) -> PyResult> { @@ -320,7 +328,10 @@ fn run_dbt_sqlbuild_work<'py>( /// Report a SQLBuild skip transition without writing machine output to stdout. #[pyfunction] #[pyo3(signature = (**options))] -fn write_sqlbuild_skip_notice(py: Python<'_>, options: Option>) -> PyResult<()> { +pub(crate) fn write_sqlbuild_skip_notice( + py: Python<'_>, + options: Option>, +) -> PyResult<()> { let args = KeywordArguments::read(py, "write_sqlbuild_skip_notice", options, OPTIONS)?; emit(&args.value("invocation")?.getattr("output_stream")?, "\n")?; progress( 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 94ba27b93e..7e9690b162 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 @@ -5,10 +5,13 @@ use crate::bindings::_helpers::compile_driver::project::build_compiled_project; use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, call_with}; use crate::bindings::_helpers::contracts::spec_resolution::resolve_effective_adapter_name; +use crate::bindings::_helpers::dbt_interop::cli::resolve_dbt_executable; use crate::bindings::_helpers::dbt_interop::cli::route_dbt_interop_args; use crate::bindings::_helpers::dbt_interop::graph::build_dbt_combined_graph; use crate::bindings::_helpers::dbt_interop::manifest::load_dbt_manifest_index; use crate::bindings::_helpers::dbt_interop::orchestration::plan_dbt_interop_command; +use crate::bindings::_helpers::dbt_interop::plan_output::dbt_failure_detail; +use crate::bindings::_helpers::dbt_interop::runner::DbtRunner; use crate::bindings::_helpers::records::construct::new_record_with; use pyo3::prelude::{ Bound, PyAny, PyAnyMethods, PyErr, PyModule, PyModuleMethods, PyResult, Python, @@ -20,7 +23,7 @@ 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::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; use pyo3::{pyfunction, wrap_pyfunction}; use std::time::Instant; @@ -74,7 +77,7 @@ pub(crate) fn resolve_dbt_execution_invocation<'py>( let py = request.py(); let mut executable = native_field(request, "dbt_executable")?; if !executable.is_truthy()? { - executable = registry::native(py, NativeAttr::ResolveDbtExecutable)?.call0()?; + executable = pyo3::IntoPyObjectExt::into_bound_py_any(resolve_dbt_executable(), py)?; } let mut stream = native_field(request, "progress_stream")?; if !stream.is_truthy()? { @@ -102,10 +105,11 @@ pub(crate) fn resolve_dbt_execution_invocation<'py>( .into_any(); let mut runner = native_field(request, "dbt_runner")?; if !runner.is_truthy()? { - runner = call_with( - ®istry::native(py, NativeAttr::DbtRunner)?, - &[("dbt_executable", executable.clone())], - )?; + runner = pyo3::Bound::new( + py, + DbtRunner::new(executable.clone().extract::>()?, None), + ) + .map(pyo3::Bound::into_any)?; } let dbt_stream = native_field(request, "dbt_stdout_stream")?; new_record_with( @@ -138,7 +142,7 @@ pub(crate) fn resolve_dbt_execution_invocation<'py>( /// Compile dbt and load the manifest after a successful subprocess result. #[pyfunction] #[pyo3(signature = (**options))] -fn load_compiled_dbt_manifest<'py>( +pub(crate) fn load_compiled_dbt_manifest<'py>( py: Python<'py>, options: Option>, ) -> PyResult> { @@ -154,7 +158,7 @@ fn load_compiled_dbt_manifest<'py>( ], )?; if native_field(&result, "returncode")?.extract::()? != 0 { - let detail = registry::native(py, NativeAttr::DbtFailureDetail)?.call1((result,))?; + let detail = dbt_failure_detail(&result)?; let error = registry::error(py, ErrorId::DbtInteropRuntime)?.call( ("dbt compile failed",), Some(&keywords(py, &[("help", detail)])?), @@ -173,7 +177,7 @@ fn load_compiled_dbt_manifest<'py>( /// Compile SQLBuild models against dbt references using the existing adapter boundary. #[pyfunction] #[pyo3(signature = (**options))] -fn compile_dbt_interop_project<'py>( +pub(crate) fn compile_dbt_interop_project<'py>( py: Python<'py>, options: Option>, ) -> PyResult> { @@ -220,7 +224,7 @@ fn compile_dbt_interop_project<'py>( /// Build the combined graph and resolve its dbt and SQLBuild selections. #[pyfunction] #[pyo3(signature = (**options))] -fn resolve_dbt_interop_plan<'py>( +pub(crate) fn resolve_dbt_interop_plan<'py>( py: Python<'py>, options: Option>, ) -> PyResult> { 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 59756ca58f..669180f0db 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 @@ -243,7 +243,7 @@ pub(crate) fn build_sqlbuild_plan_output<'py>( /// Attach live SQLBuild planning when the selection contains SQLBuild models. #[pyfunction] #[pyo3(signature = (**options))] -fn attach_sqlbuild_plan_output<'py>( +pub(crate) fn attach_sqlbuild_plan_output<'py>( py: Python<'py>, options: Option>, ) -> PyResult> { @@ -300,7 +300,7 @@ fn attach_sqlbuild_plan_output<'py>( /// Prefer stderr for dbt subprocess failure details, retaining Python whitespace semantics. #[pyfunction] -fn dbt_failure_detail<'py>(result: &Bound<'py, PyAny>) -> PyResult> { +pub(crate) fn dbt_failure_detail<'py>(result: &Bound<'py, PyAny>) -> PyResult> { let stderr = native_field(result, "stderr")?; let detail = if stderr.is_truthy()? { stderr 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 4e5549bcb6..59581f6a0d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/runner.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/runner.rs @@ -45,7 +45,7 @@ pub(crate) struct DbtRunner { impl DbtRunner { #[new] #[pyo3(signature = (*, dbt_executable = None, invoker = None))] - fn new(dbt_executable: Option, invoker: Option>) -> Self { + pub(crate) fn new(dbt_executable: Option, invoker: Option>) -> Self { Self { dbt_executable: dbt_executable .filter(|value| !value.is_empty()) 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 b52995fcee..ecca3e3a25 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,5 +1,6 @@ //! Execute dbt through the subprocess boundary and finalize results in artifact order. +use crate::bindings::_helpers::build_progress::transient_status::TransientStatusReporter; use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, keywords, }; @@ -8,10 +9,10 @@ use crate::bindings::_helpers::dbt_interop::cli::parse_dbt_ls_json_lines; use crate::bindings::_helpers::dbt_interop::event_stream::execute_dbt_json_event_stream; use crate::bindings::_helpers::dbt_interop::execution::dbt_execution_ls_argv; use crate::bindings::_helpers::dbt_interop::runtime_results::{append_missing, result_ids}; +use crate::bindings::_helpers::presentation_text::style::PythonCliStyle; use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::ids::RecordId; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::types::{PyDict, PyInt, PyString, PyTuple}; use pyo3::{pyfunction, wrap_pyfunction}; @@ -53,13 +54,14 @@ fn expected_total<'py>( if ls.is_none() { return Ok(py.None().into_bound(py)); } - let status = call_with( - ®istry::native(py, NativeAttr::TransientStatusReporter)?, - &[ - ("stream", arguments.value("progress_stream")?), - ("use_color", arguments.value("use_color")?), - ], - )?; + let status = TransientStatusReporter::new( + py, + &arguments.value("progress_stream")?, + arguments.value("use_color")?.extract::()?, + true, + ) + .and_then(|value| pyo3::Bound::new(py, value)) + .map(pyo3::Bound::into_any)?; status.call_method1("start", ("Resolving dbt execution selection...",))?; let start = Instant::now(); let result = call_with( @@ -113,10 +115,8 @@ pub(crate) fn execute_dbt_commands<'py>( let arguments = KeywordArguments::read(py, "execute_dbt_commands", options, OPTIONS)?; let argv = arguments.value("merged_argv")?; let output = arguments.value("progress_stream")?; - let style = call_with( - ®istry::native(py, NativeAttr::CliStyle)?, - &[("use_color", arguments.value("use_color")?)], - )?; + let style = Bound::new(py, PythonCliStyle::new(&arguments.value("use_color")?)?) + .map(Bound::into_any)?; if argv.is_none() { let message = arguments.value("skip_message")?; let message = if message.is_none() { 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 2e033efe8b..9c6b9873ad 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 @@ -12,17 +12,20 @@ use crate::bindings::_helpers::build_command::policies::enforce_plan_safety_poli use crate::bindings::_helpers::build_command::support::{ boolean, emit, joined, keyword_relay, none, text, tuple, }; +use crate::bindings::_helpers::build_progress::build_callbacks::BuildProgressCallbacks; use crate::bindings::_helpers::build_progress::footer::format_build_footer; +use crate::bindings::_helpers::cli::progress_reporters::ConnectionProgressReporter; use crate::bindings::_helpers::cli::progress_text::format_execution_header; use crate::bindings::_helpers::compile_driver::python_calls::{ KeywordArguments, call_with, keywords, }; +use crate::bindings::_helpers::compile_outputs::runtime_target::write_runtime_target; use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; +use crate::bindings::_helpers::presentation_text::style::PythonCliStyle; use crate::bindings::_helpers::records::construct::{new_record_with, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::ids::RecordId; const WORK_OPTIONS: (&[&str], &[&str]) = ( &[ @@ -111,15 +114,20 @@ pub(crate) fn execute_dbt_sqlbuild_work<'py>( )?), ) .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; - let callbacks = call_with( - ®istry::native(py, NativeAttr::BuildProgressCallbacks)?, - &[ - ("plan", plan.clone()), - ("use_color", color.clone()), - ("verbose", args.value("verbose")?), - ("debug", boolean(py, false)), - ], - )?; + let callbacks = BuildProgressCallbacks::new( + py, + Some(keywords( + py, + &[ + ("plan", plan.clone()), + ("use_color", color.clone()), + ("verbose", args.value("verbose")?), + ("debug", boolean(py, false)), + ], + )?), + ) + .and_then(|value| pyo3::Bound::new(py, value)) + .map(pyo3::Bound::into_any)?; let command = args.value("command")?; let kinds = record_class(py, RecordId::DbtInteropCommand)?; let display = if command.eq(native_field(&kinds, "RUN")?)? { @@ -139,10 +147,7 @@ pub(crate) fn execute_dbt_sqlbuild_work<'py>( ), py, )?; - let style = call_with( - ®istry::native(py, NativeAttr::CliStyle)?, - &[("use_color", color.clone())], - )?; + let style = Bound::new(py, PythonCliStyle::new(&color.clone())?).map(Bound::into_any)?; emit( &output, &format!( @@ -155,15 +160,20 @@ pub(crate) fn execute_dbt_sqlbuild_work<'py>( .extract::()? ), )?; - let connection = call_with( - ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, - &[ - ("adapter_name", native_field(&context, "adapter_name")?), - ("stream", output.clone()), - ("blank_line_after_complete", boolean(py, true)), - ("use_color", color.clone()), - ], - )?; + let connection = ConnectionProgressReporter::new( + py, + Some(keywords( + py, + &[ + ("adapter_name", native_field(&context, "adapter_name")?), + ("stream", output.clone()), + ("blank_line_after_complete", boolean(py, true)), + ("use_color", color.clone()), + ], + )?), + ) + .and_then(|value| pyo3::Bound::new(py, value)) + .map(pyo3::Bound::into_any)?; let build = command.eq(native_field(&kinds, "BUILD")?)?; let runtime = call_with( &host::entry(py, ExecutorHost::BuildRuntimeParams)?, @@ -228,16 +238,10 @@ pub(crate) fn execute_dbt_sqlbuild_work<'py>( ("callbacks", hooks), ], )?; - call_with( - ®istry::native(py, NativeAttr::WriteRuntimeTarget)?, - &[ - ( - "target_dir", - joined(&args.value("project_dir")?, &["target"])?, - ), - ("plan_output", plan), - ("result", result.clone()), - ], + write_runtime_target( + &joined(&args.value("project_dir")?, &["target"])?, + &plan, + &result, )?; let footer = format_build_footer( &result.clone(), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/diagnostics/trackers.rs b/crates/sqlbuild-python/src/bindings/_helpers/diagnostics/trackers.rs index 51334aaf6e..1171a8dcb2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/diagnostics/trackers.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/diagnostics/trackers.rs @@ -75,7 +75,7 @@ pub(crate) struct BuildPhaseTimingTracker { impl BuildPhaseTimingTracker { #[new] #[pyo3(signature = (*, monotonic = None))] - fn new(py: Python<'_>, monotonic: Option>) -> PyResult { + pub(crate) fn new(py: Python<'_>, monotonic: Option>) -> PyResult { let monotonic: Bound<'_, PyAny> = clock(py, monotonic)?; let started_at: f64 = monotonic.call0()?.extract()?; Ok(Self { 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 27dc07328f..5d3313059e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/diff_report/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/diff_report/command.rs @@ -17,9 +17,14 @@ use sqlbuild_cli::diff_report::types::QueryDiffPhase; use sqlbuild_core::json::main::dumps::dumps; use sqlbuild_core::json::models::{JsonDialect, StdlibJsonOptions}; +use crate::bindings::_helpers::command_runtime::connection::resolve_target_connection_config; +use crate::bindings::_helpers::compile_driver::diff::run_diff_pipeline; use crate::bindings::_helpers::compile_driver::discovery::discover_project_inputs; use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::compile_driver::python_calls::keywords; +use crate::bindings::_helpers::contracts::spec_resolution::resolve_target_config; +use crate::bindings::_helpers::contracts::spec_resolution::resolve_target_name; +use crate::bindings::_helpers::dbt_interop::reference_resolver::resolve_external_sql_reference_resolver; use crate::bindings::_helpers::diff_report::facts::diff_facts; use crate::bindings::_helpers::diff_report::query::{ execute_query_diff, execution_options, prepare_query_diff, project_adapter, @@ -34,8 +39,9 @@ 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::planner_selection::unit_tests::reject_unit_test_selectors; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr}; +use crate::bindings::_helpers::records::ids::ErrorId; use crate::bindings::_helpers::records::registry; const DEFAULT_DIFF_MAX_FULL_ROWS: i64 = 10_000_000; @@ -90,13 +96,10 @@ fn resolve_invocation<'py>( Some(keywords(py, &[("project_dir", project_dir.clone())])?), )? }; - call( - ®istry::native(py, NativeAttr::RejectUnitTestSelectors)?, - &[ - ("select", &native_field(request, "select")?), - ("exclude", &native_field(request, "exclude")?), - ("discovered_inputs", &discovered), - ], + reject_unit_test_selectors( + &native_field(request, "select")?, + &native_field(request, "exclude")?, + &discovered.clone(), )?; if facts.select.is_empty() && !query_mode { return Err(rejected( @@ -226,16 +229,11 @@ fn target_max_full_rows<'py>( target: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = discovered.py(); - let configured: Bound<'py, PyAny> = call( - ®istry::native(py, NativeAttr::ResolveTargetConfig)?, - &[ - ( - "project_config", - &native_field(discovered, "project_config")?, - ), - ("local_config", &native_field(discovered, "local_config")?), - ("target_name", target), - ], + let configured: Bound<'py, PyAny> = resolve_target_config( + py, + &native_field(discovered, "project_config")?, + &native_field(discovered, "local_config")?, + target, )? .getattr("diff")? .getattr("max_full_rows")?; @@ -259,17 +257,13 @@ fn diff_to_target<'py>( if !to_name.is_none() { return Ok(to_name); } - let active: Bound<'py, PyAny> = call( - ®istry::native(py, NativeAttr::ResolveTargetName)?, - &[ - ( - "project_config", - &native_field(discovered, "project_config")?, - ), - ("local_config", &native_field(discovered, "local_config")?), - ("selected_target", &none(py)), - ], - )?; + let active: Bound<'py, PyAny> = resolve_target_name( + py, + &native_field(discovered, "project_config")?, + &native_field(discovered, "local_config")?, + &none(py), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; if active.is_none() { return Err(rejected( py, @@ -381,40 +375,42 @@ fn run_model_diff( let from_target: Bound<'_, PyAny> = object(py, from_name)?; let (_, adapter) = project_adapter(discovered, project_dir)?; let cli_vars: Bound<'_, PyAny> = native_field(request, "cli_vars")?; - let connection_config: Bound<'_, PyAny> = call( - ®istry::native(py, NativeAttr::ResolveTargetConnectionConfig)?, - &[ - ("discovered_inputs", discovered), - ("project_dir", project_dir), - ("target_name", &to_target), - ("cli_vars", &cli_vars), - ], - )?; - let resolver: Bound<'_, PyAny> = call( - ®istry::native(py, NativeAttr::ResolveExternalSqlReferenceResolver)?, - &[ - ("project_dir", project_dir), - ("discovered_inputs", discovered), - ], + let connection_config: Bound<'_, PyAny> = resolve_target_connection_config( + py, + Some(keywords( + py, + &[ + ("discovered_inputs", discovered.clone()), + ("project_dir", project_dir.clone()), + ("target_name", to_target.clone()), + ("cli_vars", cli_vars.clone()), + ], + )?), )?; - let pipeline: Bound<'_, PyTuple> = call( - ®istry::native(py, NativeAttr::RunDiffPipeline)?, - &[ - ("discovered_inputs", discovered), - ("adapter", &adapter), - ("from_target", &from_target), - ("to_target", &to_target), - ("resolved_connection", &connection_config), - ( - "no_sql_validation", - &native_field(request, "no_sql_validation")?, - ), - ("select", &native_field(request, "select")?), - ("exclude", &native_field(request, "exclude")?), - ("cli_vars", &cli_vars), - ("external_sql_reference_resolver", &resolver), - ], - )? + let resolver: Bound<'_, PyAny> = + resolve_external_sql_reference_resolver(project_dir.clone(), discovered.clone())?; + let pipeline: Bound<'_, PyTuple> = run_diff_pipeline( + py, + Some(keywords( + py, + &[ + ("discovered_inputs", discovered.clone()), + ("adapter", adapter.clone()), + ("from_target", from_target.clone()), + ("to_target", to_target.clone()), + ("resolved_connection", connection_config.clone()), + ( + "no_sql_validation", + native_field(request, "no_sql_validation")?, + ), + ("select", native_field(request, "select")?), + ("exclude", native_field(request, "exclude")?), + ("cli_vars", cli_vars.clone()), + ("external_sql_reference_resolver", resolver.clone()), + ], + )?), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))? .downcast_into()?; let selected: Bound<'_, PyAny> = pipeline.get_item(2)?; if !selected.is_truthy()? { 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 1e1f8cd1cd..07b40f56bb 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/diff_report/query.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/diff_report/query.rs @@ -14,7 +14,11 @@ use sqlbuild_cli::diff_report::types::PhaseSeconds; 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::imported; +use crate::bindings::_helpers::command_runtime::connection::resolve_project_connection_config; +use crate::bindings::_helpers::compile_attachment::effective_config::build_effective_runtime_config; +use crate::bindings::_helpers::compile_attachment::target::build_effective_target_namespace; +use crate::bindings::_helpers::compile_driver::python_calls::{imported, keywords}; +use crate::bindings::_helpers::contracts::spec_resolution::resolve_effective_adapter_name; use crate::bindings::_helpers::diff_report::support::{ call, cli_error, error_text, none, object, progress_line, }; @@ -24,8 +28,7 @@ use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::records::construct::record_class; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::types::CompilerDetach; use sqlbuild_analysis::diff_selection::main::raw_query_shape::raw_query_shape; use sqlbuild_analysis::diff_selection::models::RawQueryShape; @@ -54,16 +57,9 @@ pub(crate) fn project_adapter<'py>( discovered: &Bound<'py, PyAny>, project_dir: &Bound<'py, PyAny>, ) -> PyResult<(String, Bound<'py, PyAny>)> { - let py: Python<'py> = discovered.py(); - let name: Bound<'py, PyAny> = call( - ®istry::native(py, NativeAttr::ResolveEffectiveAdapterName)?, - &[ - ( - "project_config", - &native_field(discovered, "project_config")?, - ), - ("local_config", &native_field(discovered, "local_config")?), - ], + let name: Bound<'py, PyAny> = resolve_effective_adapter_name( + &native_field(discovered, "project_config")?, + &native_field(discovered, "local_config")?, )?; let adapter: Bound<'py, PyAny> = resolve_cli_adapter(&name, project_dir)?; Ok((name.extract()?, adapter)) @@ -174,37 +170,44 @@ pub(crate) fn prepare_query_diff<'py>( let (adapter_name, adapter) = project_adapter(discovered, project_dir)?; let selected_target: Bound<'py, PyAny> = native_field(request, "selected_target")?; let cli_vars: Bound<'py, PyAny> = native_field(request, "cli_vars")?; - let config: Bound<'py, PyAny> = call( - ®istry::native(py, NativeAttr::ResolveProjectConnectionConfig)?, - &[ - ("discovered_inputs", discovered), - ("project_dir", project_dir), - ("selected_target", &selected_target), - ("cli_vars", &cli_vars), - ], + let config: Bound<'py, PyAny> = resolve_project_connection_config( + py, + Some(keywords( + py, + &[ + ("discovered_inputs", discovered.clone()), + ("project_dir", project_dir.clone()), + ("selected_target", selected_target.clone()), + ("cli_vars", cli_vars.clone()), + ], + )?), )?; let database: Bound<'py, PyAny> = if NAMESPACELESS_ADAPTERS.contains(&adapter_name.as_str()) { none(py) } else { config.call_method1("get", ("database",))? }; - let namespace: Bound<'py, PyAny> = call( - ®istry::native(py, NativeAttr::BuildEffectiveTargetNamespace)?, - &[ - ("discovered_inputs", discovered), - ("selected_target", &selected_target), - ("connection_database", &database), - ( - "connection_schema", - &config.call_method1("get", ("schema",))?, - ), - ( - "default_database", - &adapter.call_method0("default_database")?, - ), - ("default_schema", &adapter.call_method0("default_schema")?), - ], - )?; + let namespace: Bound<'py, PyAny> = build_effective_target_namespace( + py, + Some(keywords( + py, + &[ + ("discovered_inputs", discovered.clone()), + ("selected_target", selected_target.clone()), + ("connection_database", database.clone()), + ( + "connection_schema", + config.call_method1("get", ("schema",))?, + ), + ( + "default_database", + adapter.call_method0("default_database")?, + ), + ("default_schema", adapter.call_method0("default_schema")?), + ], + )?), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; let (target_name, _target, database, schema): ( Bound<'py, PyAny>, Bound<'py, PyAny>, @@ -233,14 +236,12 @@ 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( - ®istry::native(py, NativeAttr::BuildEffectiveRuntimeConfig)?, - &[ - ("discovered_inputs", discovered), - ("selected_target", &selected_target), - ("cli_vars", &cli_vars), - ], - )? + let runtime: Bound<'py, PyTuple> = build_effective_runtime_config( + discovered, + Some(selected_target.clone()).filter(|value| !value.is_none()), + Some(cli_vars.clone()).filter(|value| !value.is_none()), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))? .downcast_into()?; let label = |name: &str, default: &str| -> PyResult { let value: Bound<'py, PyAny> = request.getattr(name)?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/diff_report/registration.rs b/crates/sqlbuild-python/src/bindings/_helpers/diff_report/registration.rs index 088a0352a2..af9c4d14b3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/diff_report/registration.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/diff_report/registration.rs @@ -119,7 +119,10 @@ fn render_diff_output(py: Python<'_>, options: Option>) -> PyR /// Render a machine-readable incomplete or execution-failure outcome. #[pyfunction] #[pyo3(signature = (**options))] -fn render_diff_error_json(py: Python<'_>, options: Option>) -> PyResult { +pub(crate) fn render_diff_error_json( + py: Python<'_>, + options: Option>, +) -> PyResult { let arguments: KeywordArguments<'_> = KeywordArguments::read(py, "render_diff_error_json", options, ERROR_OPTIONS)?; let size_guard: Bound<'_, PyAny> = arguments.value("size_guard")?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/model_records.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/model_records.rs index e7bba18f30..8863216968 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/model_records.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/model_records.rs @@ -103,7 +103,7 @@ pub(crate) fn discovered_model_files<'py>( /// Discover the (selected) model files natively, reporting failing files to `on_fault`. #[pyfunction] #[pyo3(signature = (**options))] -fn discover_model_files<'py>( +pub(crate) fn discover_model_files<'py>( py: Python<'py>, options: Option>, ) -> PyResult> { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/snapshot.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/snapshot.rs index 6aa4fcd003..54668afd05 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/snapshot.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/snapshot.rs @@ -153,7 +153,9 @@ impl NativeDirectorySnapshotScope { /// Share one directory snapshot across every discovery call for `project_dir` inside the scope. #[pyfunction] #[pyo3(signature = (*, project_dir))] -fn directory_snapshot_scope(project_dir: Bound<'_, PyAny>) -> NativeDirectorySnapshotScope { +pub(crate) fn directory_snapshot_scope( + project_dir: Bound<'_, PyAny>, +) -> NativeDirectorySnapshotScope { NativeDirectorySnapshotScope { project_dir: project_dir.unbind(), token: None, 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 80863e67d3..fc8ee39c87 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/dispatcher.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/dispatcher.rs @@ -610,7 +610,7 @@ fn start_worker<'py>( impl EventExporterDispatcher { #[new] #[pyo3(signature = (**options))] - fn new(py: Python<'_>, options: Option>) -> PyResult { + pub(crate) fn new(py: Python<'_>, options: Option>) -> PyResult { let options: KeywordArguments<'_> = KeywordArguments::read( py, "EventExporterDispatcher", 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 9179f5318a..be96283bf9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/scope.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/scope.rs @@ -202,7 +202,7 @@ impl EventExporterCommandScope { impl EventExporterCommandScope { #[new] #[pyo3(signature = (*, dispatcher))] - fn new(dispatcher: Py) -> Self { + pub(crate) fn new(dispatcher: Py) -> Self { Self { dispatcher, state: Mutex::new(ScopeState { @@ -411,7 +411,7 @@ impl EventExporterScopeContext { /// Install one command-owned exporter scope. #[pyfunction] -fn event_exporter_command_scope(scope: Py) -> EventExporterScopeContext { +pub(crate) fn event_exporter_command_scope(scope: Py) -> EventExporterScopeContext { EventExporterScopeContext { scope, token: Mutex::new(None), 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 67f7efd85d..8790e33711 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/format_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/format_command/command.rs @@ -263,7 +263,10 @@ fn run_command( /// Run one `sqb format` invocation. #[pyfunction] #[pyo3(signature = (**options))] -fn run_format_command<'py>(py: Python<'py>, options: Option>) -> PyResult { +pub(crate) fn run_format_command<'py>( + py: Python<'py>, + options: Option>, +) -> PyResult { let arguments: KeywordArguments<'py> = KeywordArguments::read( py, "run_format_command", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/format_command/files.rs b/crates/sqlbuild-python/src/bindings/_helpers/format_command/files.rs index 7f607c3774..0b4e817f02 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/format_command/files.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/format_command/files.rs @@ -91,7 +91,7 @@ fn write_atomically(path: &Path, contents: &str) -> PyResult<()> { /// `write_atomically(*, path, contents)`: replace a text file atomically, keeping its mode. #[pyfunction] #[pyo3(name = "write_atomically", signature = (*, path, contents))] -fn write_file_atomically(path: PathBuf, contents: &str) -> PyResult<()> { +pub(crate) fn write_file_atomically(path: PathBuf, contents: &str) -> PyResult<()> { write_atomically(&path, contents) } 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 5cc17ddfc7..341895c43e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/format_command/run.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/format_command/run.rs @@ -4,22 +4,29 @@ use std::collections::BTreeSet; use std::path::PathBuf; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; -use pyo3::types::{PyBool, PyDict, PyDictMethods, PyList, PyListMethods, PyTuple}; +use pyo3::types::{PyBool, PyDict, PyDictMethods, PyList, PyListMethods}; use sqlbuild_core::format_command::main::collect_lint_files::collect_lint_files; use crate::bindings::_helpers::boundary::panics::value_error; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, keywords}; +use crate::bindings::_helpers::compile_driver::python_calls::keywords; +use crate::bindings::_helpers::compile_driver::stage_work::{ + FORMAT_LINT_BATCHES, FORMAT_LINT_FILES, count_stage, +}; 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::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr}; +use crate::bindings::_helpers::records::ids::ErrorId; use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::rules::dsl_headers::format_native_headers; +use crate::bindings::_helpers::rules::dsl_headers::reject_unparseable_header_rewrites; +use crate::bindings::_helpers::rules::dsl_headers::safe_format_files; use crate::bindings::_helpers::rules::dsl_headers::{header_violations, rules_config}; use crate::bindings::_helpers::rules::fixture_formatting::format_redundant_fixture_nulls; use crate::bindings::_helpers::rules::lint_pipeline::{LintViolationRow, ViolationClasses}; use crate::bindings::_helpers::rules::lint_text::apply_lint_suppressions; +use crate::bindings::_helpers::rules::lint_text::format_lint_sql_bodies; use sqlbuild_rules::dsl_headers::main::scan_headers::scan_headers; use sqlbuild_rules::dsl_headers::models::{HeaderRulesConfig, HeaderViolation}; @@ -48,10 +55,6 @@ pub(crate) struct FormatOutcome<'py> { pub(crate) final_contents: Bound<'py, PyDict>, } -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. fn fixture_autofix<'py>( request: &FormatRequest<'_, 'py>, @@ -78,15 +81,19 @@ fn format_bodies<'py>( files: &Bound<'py, PyDict>, ) -> PyResult<(Bound<'py, PyDict>, Bound<'py, PyAny>)> { let py: Python<'py> = files.py(); - let items: Vec> = sorted_items(files)? + let items: Vec<(Bound<'py, PyAny>, String)> = sorted_items(files)? .into_iter() - .map(|(path, contents)| Ok(PyTuple::new(py, [path, contents])?.into_any())) + .map(|(path, contents)| Ok((path, contents.extract::()?))) .collect::>()?; - let result: Bound<'py, PyAny> = native(py, NativeAttr::FormatLintSqlBodies)?.call1(( - PyList::new(py, items)?, - request.project_dir, + count_stage(py, FORMAT_LINT_BATCHES, 1)?; + count_stage(py, FORMAT_LINT_FILES, items.len() as u64)?; + let result: Bound<'py, PyAny> = format_lint_sql_bodies( + py, + items, + request.project_dir.extract::()?, request.config, - ))?; + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; Ok(( result.get_item(0)?.downcast_into::()?, result.get_item(1)?, @@ -107,14 +114,9 @@ fn apply_fixes<'py>( } let current: Bound<'py, PyDict> = overlaid(files, &updated)?; for (path, contents) in sorted_items(¤t)? { - let result: Bound<'py, PyAny> = call_with( - &native(py, NativeAttr::FormatNativeHeaders)?, - &[ - ("contents", contents.clone()), - ("file_path", path.clone()), - ("config", request.config.clone()), - ], - )?; + let result: Bound<'py, PyAny> = + format_native_headers(&contents.clone(), &path.clone(), &request.config.clone()) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; for fault in result.get_item(1)?.try_iter()? { faults.append(fault?)?; } @@ -138,14 +140,12 @@ fn apply_fixes<'py>( updated.update(fixture_formatted.as_mapping())?; updated.update(final_native.as_mapping())?; } - let result: Bound<'py, PyAny> = call_with( - &native(py, NativeAttr::RejectUnparseableHeaderRewrites)?, - &[ - ("updated", updated.into_any()), - ("config", request.config.clone()), - ("faults", faults.into_any()), - ], - )?; + let result: Bound<'py, PyAny> = reject_unparseable_header_rewrites( + &updated.into_any().cast_into::()?, + &request.config.clone(), + &faults.into_any().cast_into::()?, + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; Ok(( result.get_item(0)?.downcast_into::()?, result.get_item(1)?.downcast_into::()?, @@ -269,13 +269,11 @@ pub(crate) fn run_format<'py>(request: &FormatRequest<'_, 'py>) -> PyResult

= file_dictionary(py, collected)?; - let safe: Bound<'py, PyAny> = call_with( - &native(py, NativeAttr::SafeFormatFiles)?, - &[ - ("files", files.clone().into_any()), - ("config", request.config.clone()), - ], - )?; + let safe: Bound<'py, PyAny> = safe_format_files( + &files.clone().into_any().cast_into::()?, + &request.config.clone(), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; let newlines: Bound<'py, PyDict> = safe.get_item(1)?.downcast_into::()?; let (safe_files, rule_fixes, rule_faults) = plan_rule_fixes(request, &safe.get_item(0)?.downcast_into::()?)?; 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 9209dcfcd0..dc6ccc1413 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/format_command/selection.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/format_command/selection.rs @@ -18,13 +18,22 @@ use crate::bindings::_helpers::command_runtime::adapters::resolve_cli_adapter; use crate::bindings::_helpers::compile_driver::discovery::discover_project_inputs; use crate::bindings::_helpers::compile_driver::project::build_project_graph; use crate::bindings::_helpers::compile_driver::python_calls::keywords; -use crate::bindings::_helpers::compile_driver::python_calls::{call_with, within}; +use crate::bindings::_helpers::compile_driver::python_calls::within; +use crate::bindings::_helpers::contracts::project_config::load_local_config_file; +use crate::bindings::_helpers::contracts::project_config::load_project_config_file; use crate::bindings::_helpers::contracts::spec_resolution::resolve_effective_adapter_name; +use crate::bindings::_helpers::discovery::declaration_records::discover_model_schema_files; +use crate::bindings::_helpers::discovery::model_records::discover_model_files; +use crate::bindings::_helpers::discovery::snapshot::directory_snapshot_scope; +use crate::bindings::_helpers::discovery::sql_test_records::discover_test_files; +use crate::bindings::_helpers::discovery::yaml_records::{ + discover_schema_files, discover_source_files, +}; use crate::bindings::_helpers::planner_selection::selectors::select_project_graph; use crate::bindings::_helpers::planner_selection::unit_tests::reject_unit_test_selectors; use crate::bindings::_helpers::records::construct::{enum_member, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; const SELECTION_CODE: &str = "S007"; @@ -241,20 +250,16 @@ fn selected_contract_inputs<'py>( referenced_model_names: Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = project_dir.py(); - let native = |name: NativeAttr| registry::native(py, name); - let project_config: Bound<'py, PyAny> = - native(NativeAttr::LoadProjectConfigFile)?.call1((project_dir,))?; - let local_config: Bound<'py, PyAny> = - native(NativeAttr::LoadLocalConfigFile)?.call1((project_dir,))?; + let project_config: Bound<'py, PyAny> = load_project_config_file(py, project_dir) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; + let local_config: Bound<'py, PyAny> = load_local_config_file(py, project_dir) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; let disabled: Bound<'py, PyAny> = PyBool::new(py, false).to_owned().into_any(); - let scope: Bound<'py, PyAny> = call_with( - &native(NativeAttr::DirectorySnapshotScope)?, - &[("project_dir", project_dir.clone())], + let scope: Bound<'py, PyAny> = pyo3::IntoPyObjectExt::into_bound_py_any( + directory_snapshot_scope(project_dir.clone()), + py, )?; within(&scope, |_| { - let project_only = |name: NativeAttr| -> PyResult> { - call_with(&native(name)?, &[("project_dir", project_dir.clone())]) - }; new_record_with( py, RecordId::DiscoveredProjectInputs, @@ -264,37 +269,40 @@ fn selected_contract_inputs<'py>( ("project_dir", project_dir.clone()), ( "model_files", - call_with( - &native(NativeAttr::DiscoverModelFiles)?, - &[ - ("project_dir", project_dir.clone()), - ("extract_implicit_alias_columns", disabled.clone()), - ("extract_output_column_locations", disabled.clone()), - ("selected_model_names", referenced_model_names), - ], - )?, + discover_model_files( + py, + Some(keywords( + py, + &[ + ("project_dir", project_dir.clone()), + ("extract_implicit_alias_columns", disabled.clone()), + ("extract_output_column_locations", disabled.clone()), + ("selected_model_names", referenced_model_names), + ], + )?), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?, ), ( "model_schema_files", - project_only(NativeAttr::DiscoverModelSchemaFiles)?, + discover_model_schema_files(project_dir, None)?.into_any(), ), ( "schema_files", - project_only(NativeAttr::DiscoverSchemaFiles)?, + discover_schema_files(project_dir)?.into_any(), ), ( "source_files", - project_only(NativeAttr::DiscoverSourceFiles)?, + discover_source_files(project_dir, None)?.into_any(), ), ( "test_files", - call_with( - &native(NativeAttr::DiscoverTestFiles)?, - &[ - ("project_dir", project_dir.clone()), - ("selected_paths", selected_test_paths), - ], - )?, + discover_test_files( + &project_dir.clone(), + Some(selected_test_paths).filter(|value| !value.is_none()), + None, + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?, ), ], ) 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 d5a7a1d512..d3bd24ebe5 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/freshness/freshness_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/freshness/freshness_command.rs @@ -269,7 +269,7 @@ fn write_json_output( /// Run one `sqb freshness` request. #[pyfunction] #[pyo3(signature = (*, request))] -fn run_freshness_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { +pub(crate) fn run_freshness_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { let fail_on_stale: bool = native_field(request, "fail_on_stale")?.extract()?; let fail_on_error: bool = native_field(request, "fail_on_error")?.extract()?; if fail_on_stale && !native_field(request, "compare_state")?.extract::()? { 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 2c1d0ec1e1..d6c43b6c66 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/graph/execution_gates.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/graph/execution_gates.rs @@ -89,7 +89,7 @@ fn hook_read_edges<'py>( /// The Python hooks attached to one model, in lifecycle order without repeats. #[pyfunction] #[pyo3(signature = (*, model))] -fn model_python_hook_names<'py>( +pub(crate) fn model_python_hook_names<'py>( py: Python<'py>, model: &Bound<'py, PyAny>, ) -> PyResult> { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/hand_built/compiled_model.rs b/crates/sqlbuild-python/src/bindings/_helpers/hand_built/compiled_model.rs index db9231069e..f56efeff4c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/hand_built/compiled_model.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/hand_built/compiled_model.rs @@ -1,11 +1,14 @@ //! A `CompiledModel` for a model input built outside the native compile-input stage. +use crate::bindings::_helpers::analysis::binding_locations::locate_model_binding_diagnostics; +use crate::bindings::_helpers::analysis_session::outcome_objects::dynamic_contract_proof; +use crate::bindings::_helpers::analysis_session::outcome_objects::session_outcome_analysis; use crate::bindings::_helpers::records::construct::{enum_from_value, enum_member, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{NativeAttr, RecordId}; -use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::records::ids::RecordId; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::{PyDict, PyList, PyString, PyTuple}; +use sqlbuild_analysis::semantic_validation::types::{DiagnosticRow, Expansion}; type Object<'py> = Bound<'py, PyAny>; @@ -26,8 +29,8 @@ 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> = registry::native(py, NativeAttr::DynamicContractProof)? - .call1((native_field(analysis, "standalone_proof")?,))?; + let mut proof: Object<'py> = + dynamic_contract_proof(&native_field(analysis, "standalone_proof")?)?; let session: Object<'py> = native_field(analysis, "session")?; if !session.is_none() { let (session, position): (Object<'py>, Object<'py>) = session.extract()?; @@ -35,8 +38,7 @@ 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> = - registry::native(py, NativeAttr::SessionOutcomeAnalysis)?.call((), Some(&arguments))?; + let outcome: Object<'py> = session_outcome_analysis(Some(&arguments))?; let result: Object<'py> = native_field(&outcome, "polyglot_analysis")?; if native_field(&result, "analysis_succeeded")?.is_truthy()? { inferred = native_field(&result, "columns")?; @@ -261,8 +263,12 @@ fn located<'py>( )? .into_any() }; - let located: Object<'py> = registry::native(py, NativeAttr::LocateModelBindingDiagnostics)? - .call1((PyList::new(py, rows)?, texts, expansion_row))?; + let located: Object<'py> = locate_model_binding_diagnostics( + PyList::new(py, rows)?.extract::>()?, + texts.extract::<(String, String, String)>()?, + expansion_row.extract::>)>>()?, + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; let diagnostic_class: Object<'py> = record_class(py, RecordId::CompilerDiagnostic)?; let location_class: Object<'py> = record_class(py, RecordId::SourceLocation)?; let phase: Object<'py> = enum_member(py, RecordId::DiagnosticPhase, "COMPILE")?; 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 93351b790d..a434d5a90d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/init_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/init_command/command.rs @@ -38,7 +38,10 @@ fn exists_error(py: Python<'_>) -> PyErr { /// Create a minimal SQLBuild project in the project directory. #[pyfunction] #[pyo3(signature = (project_dir = None))] -fn run_init_command(py: Python<'_>, project_dir: Option>) -> PyResult { +pub(crate) fn run_init_command( + py: Python<'_>, + project_dir: Option>, +) -> PyResult { let base_dir: Bound<'_, PyAny> = match project_dir.filter(|path| !path.is_none()) { Some(path) => path, None => imported(py, "pathlib", "Path")?.call_method0("cwd")?, 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 afe4362701..f53d9c36ec 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_command.rs @@ -216,7 +216,7 @@ fn clean( /// Run one `sqb janitor` request. #[pyfunction] #[pyo3(signature = (*, request))] -fn run_janitor_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { +pub(crate) fn run_janitor_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { let invocation: Bound<'_, PyAny> = invocation(py, request)?; let style: CliStyle = CliStyle { use_color: native_field(&invocation, "use_color")?.extract()?, 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 70e23da755..a83d2f1dd9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_cache.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_cache.rs @@ -4,9 +4,8 @@ use std::collections::{HashMap, HashSet}; use std::hash::Hash; use std::path::{Path, PathBuf}; +use crate::bindings::_helpers::lineage::relation_fingerprint::relation_lineage_fingerprint; use crate::bindings::_helpers::records::fields::field as native_field; -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}; @@ -141,9 +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; - registry::native(py, NativeAttr::RelationLineageFingerprint)? - .call1((project_dir, PyBytes::new(py, &prefix), uncacheable))? - .extract() + relation_lineage_fingerprint(py, project_dir.extract()?, prefix, uncacheable) } fn rows<'py, T: pyo3::FromPyObject<'py>>( 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 2d18f3337b..b5f9a5b675 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_command.rs @@ -34,14 +34,25 @@ use sqlbuild_core::cli_text::models::CliStyle; use crate::bindings::_helpers::build_progress::transient_status::maybe_status; use crate::bindings::_helpers::command_runtime::adapters::resolve_cli_adapter; use crate::bindings::_helpers::compile_driver::discovery::discover_project_inputs; +use crate::bindings::_helpers::compile_driver::project::build_project_graph; +use crate::bindings::_helpers::contracts::spec_resolution::resolve_effective_adapter_name; use crate::bindings::_helpers::lineage::lineage_cache::{ lineage_fingerprint, read_lineage_cache, write_lineage_cache, }; use crate::bindings::_helpers::lineage::lineage_index::{graph_index, index_views, lineage_keys}; +use crate::bindings::_helpers::lineage::project_lineage::build_direct_semantic_uses; +use crate::bindings::_helpers::lineage::project_lineage::build_project_column_lineage; +use crate::bindings::_helpers::planner_selection::selectors::resolve_project_selector_tokens; +use crate::bindings::_helpers::planner_selection::selectors::resolve_project_selectors; +use crate::bindings::_helpers::planner_selection::selectors::select_project_graph; +use crate::bindings::_helpers::planner_selection::selectors::{ + match_project_selector, parse_project_selector, +}; +use crate::bindings::_helpers::planner_selection::unit_tests::reject_unit_test_selectors; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; use crate::bindings::_helpers::records::construct::record_class; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; const UNKNOWN_SELECTOR_ERROR_CODE: &str = "S007"; @@ -141,20 +152,12 @@ fn discover<'py>( discover_project_inputs(py, Some(options)) } -fn reject_unit_tests( - py: Python<'_>, - request: &Bound<'_, PyAny>, - discovered: &Bound<'_, PyAny>, -) -> PyResult<()> { - let options: Bound<'_, PyDict> = keywords( - py, - &[ - ("select", &native_field(request, "select")?), - ("exclude", &native_field(request, "exclude")?), - ("discovered_inputs", discovered), - ], +fn reject_unit_tests(request: &Bound<'_, PyAny>, discovered: &Bound<'_, PyAny>) -> PyResult<()> { + reject_unit_test_selectors( + &native_field(request, "select")?, + &native_field(request, "exclude")?, + discovered, )?; - registry::native(py, NativeAttr::RejectUnitTestSelectors)?.call((), Some(&options))?; Ok(()) } @@ -177,21 +180,11 @@ fn prepare_source<'py>( } let discovered: Bound<'py, PyAny> = discover(py, project_dir, if compiled { None } else { Some(false) })?; - reject_unit_tests(py, request, &discovered)?; - let adapter_name: Bound<'py, PyAny> = - registry::native(py, NativeAttr::ResolveEffectiveAdapterName)?.call( - (), - Some(&keywords( - py, - &[ - ( - "project_config", - &native_field(&discovered, "project_config")?, - ), - ("local_config", &native_field(&discovered, "local_config")?), - ], - )?), - )?; + reject_unit_tests(request, &discovered)?; + let adapter_name: Bound<'py, PyAny> = resolve_effective_adapter_name( + &native_field(&discovered, "project_config")?, + &native_field(&discovered, "local_config")?, + )?; let adapter: Bound<'py, PyAny> = resolve_cli_adapter(&adapter_name, project_dir)?; let no_validation: bool = native_field(request, "no_sql_validation")?.extract::()? || !compiled; @@ -205,8 +198,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> = - registry::native(py, NativeAttr::BuildProjectGraph)?.call((), Some(&options))?; + let graph: Bound<'py, PyAny> = build_project_graph(py, Some(options.clone()))?; let index: LineageIndex = graph_index(&graph)?; if let Some(fingerprint) = &fingerprint { write_lineage_cache(py, &directory, fingerprint, &index)?; @@ -266,16 +258,13 @@ fn semantic_uses( }; let targets: Vec = strings(&native_field(request, "targets")?)?; let model_names: Vec = if targets.is_empty() { - let options: Bound<'_, PyDict> = keywords( - py, - &[ - ("graph", graph), - ("select", &native_field(request, "select")?), - ("exclude", &native_field(request, "exclude")?), - ], - )?; let keys: Vec = lineage_keys( - ®istry::native(py, NativeAttr::SelectProjectGraph)?.call((), Some(&options))?, + select_project_graph( + graph, + &native_field(request, "select")?, + &native_field(request, "exclude")?, + )? + .as_any(), )?; keys.into_iter() .filter(|key| key.resource_type == MODEL_RESOURCE_TYPE) @@ -291,12 +280,15 @@ fn semantic_uses( }) .collect() }; - let uses: Bound<'_, PyAny> = - registry::native(py, NativeAttr::BuildDirectSemanticUses)?.call1(( - native_field(graph, "project")?, - adapter.call_method0("sql_analysis_dialect")?, - PyFrozenSet::new(py, model_names)?, - ))?; + let uses: Bound<'_, PyAny> = build_direct_semantic_uses( + &native_field(graph, "project")?, + adapter + .call_method0("sql_analysis_dialect")? + .extract::>()? + .as_deref(), + PyFrozenSet::new(py, model_names)?.as_any(), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; uses.try_iter()? .map(|value| semantic_use(&value?)) .collect() @@ -382,12 +374,13 @@ fn column_lineage<'py>( py, (&message, rich && count >= RICH_STATUS_MODEL_THRESHOLD), || { - registry::native(py, NativeAttr::BuildProjectColumnLineage)?.call1(( - native_field(graph, "project")?, - py.None(), + build_project_column_lineage( + &native_field(graph, "project")?, + None, mode, - PyFrozenSet::new(py, &candidates.model_names)?, - )) + Some(PyFrozenSet::new(py, &candidates.model_names)?.as_any()), + ) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py)) }, ) } @@ -477,8 +470,6 @@ fn selector_anchors( select: &[String], require_clear_anchors: bool, ) -> PyResult { - let parse: Bound<'_, PyAny> = registry::native(py, NativeAttr::ParseProjectSelector)?; - let matcher: Bound<'_, PyAny> = registry::native(py, NativeAttr::MatchProjectSelector)?; let path_class: Bound<'_, PyAny> = record_class(py, RecordId::PathSelector)?; let names: HashMap<&str, &LineageKey> = name_lookup(index); let named = |name: String| -> PyResult { @@ -500,7 +491,8 @@ fn selector_anchors( )); } for part in token.split(SELECTOR_INTERSECTION_MARKER) { - let parsed: Bound<'_, PyAny> = parse.call1((part,))?; + let parsed: Bound<'_, PyAny> = + parse_project_selector(&pyo3::types::PyString::new(py, part))?; if parsed.is_instance(&path_class)? { let start: LineageKey = named(native_field(&parsed, "start_name")?.extract()?)?; let end: LineageKey = named(native_field(&parsed, "end_name")?.extract()?)?; @@ -524,8 +516,15 @@ fn selector_anchors( } let options: Bound<'_, PyDict> = view_options(views, &["all_keys", "tag_index", "path_index"])?; - options.set_item("parsed", &parsed)?; - let matched: Vec = lineage_keys(&matcher.call((), Some(&options))?)?; + let matched: Vec = lineage_keys( + match_project_selector( + &parsed, + &options.as_any().get_item("all_keys")?, + &options.as_any().get_item("tag_index")?, + &options.as_any().get_item("path_index")?, + )? + .as_any(), + )?; let upstream: bool = native_field(&parsed, "upstream")?.extract()?; let downstream: bool = native_field(&parsed, "downstream")?.extract()?; if set_kind || upstream { @@ -557,7 +556,8 @@ fn selector_rendering( options.set_item("select", &select)?; options.set_item("exclude", &exclude)?; let selected: Vec = lineage_keys( - ®istry::native(py, NativeAttr::ResolveProjectSelectors)?.call((), Some(&options))?, + &resolve_project_selectors(py, Some(options.clone())) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?, )?; let index: &LineageIndex = source.index(); let direction: Option = native_field(request, "direction")?.extract()?; @@ -566,8 +566,8 @@ fn selector_rendering( let options: Bound<'_, PyDict> = view_options(&views, &GRAPH_VIEW_NAMES)?; options.set_item("selectors", &exclude)?; let excluded: Vec = lineage_keys( - ®istry::native(py, NativeAttr::ResolveProjectSelectorTokens)? - .call((), Some(&options))?, + &resolve_project_selector_tokens(py, Some(options.clone())) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?, )?; select_directional_lineage(index, (&selected, &excluded), (&direction, depth)) } else { @@ -594,7 +594,7 @@ fn selector_failure( { return Ok(()); } - reject_unit_tests(py, request, &discover(py, project_dir, Some(false))?) + reject_unit_tests(request, &discover(py, project_dir, Some(false))?) }; match check() { Ok(()) => error, @@ -645,7 +645,7 @@ fn request_shape(request: &Bound<'_, PyAny>) -> PyResult { /// Run one `sqb lineage` request and print its result. #[pyfunction] #[pyo3(signature = (*, request))] -fn run_lineage_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { +pub(crate) fn run_lineage_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { let shape: LineageRequestShape = request_shape(request)?; validate_lineage_request(&shape).map_err(|error| user_error(py, error))?; let depth: Option = 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 6441bd888a..ea01075063 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/lineage/project_lineage.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/lineage/project_lineage.rs @@ -64,7 +64,7 @@ pub(crate) fn build_project_column_lineage<'py>( /// Direct non-projection semantic column uses of the requested models. #[pyfunction] -fn build_direct_semantic_uses<'py>( +pub(crate) fn build_direct_semantic_uses<'py>( project: &Bound<'py, PyAny>, dialect: Option<&str>, model_names: &Bound<'py, PyAny>, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/lineage/relation_fingerprint.rs b/crates/sqlbuild-python/src/bindings/_helpers/lineage/relation_fingerprint.rs index 275c8663ea..3d85c883bc 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/lineage/relation_fingerprint.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/lineage/relation_fingerprint.rs @@ -12,7 +12,7 @@ use crate::bindings::types::CompilerDetach; /// The authored files' digest, or `None`; the flag is true where `rglob` raises mid-listing. #[pyfunction] -fn relation_lineage_fingerprint( +pub(crate) fn relation_lineage_fingerprint( py: Python<'_>, project_dir: PathBuf, prefix: Vec, 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 4d4c85f20a..88ba6f7884 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/load/load_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/load/load_command.rs @@ -16,6 +16,8 @@ use sqlbuild_core::load_command::main::render_load_plan::render_load_plan; use sqlbuild_core::load_command::main::render_load_ready::render_load_ready; use sqlbuild_core::load_command::models::{LoadCounts, LoadResultText}; +use crate::bindings::_helpers::cli::progress_reporters::ConnectionProgressReporter; +use crate::bindings::_helpers::cli::progress_text::write_execution_header; use crate::bindings::_helpers::command_runtime::adapters::resolve_cli_adapter; use crate::bindings::_helpers::command_runtime::connection::resolve_project_connection_config; use crate::bindings::_helpers::compile_attachment::effective_config::build_effective_runtime_config; @@ -36,14 +38,17 @@ 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::planner_resolution::cursor_overrides::type_cursor_overrides; use crate::bindings::_helpers::planner_selection::unit_tests::reject_unit_test_selectors; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; use crate::bindings::_helpers::records::construct::new_record; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; +use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; +use crate::bindings::_helpers::references::source_relations::render_source_relation; use crate::bindings::_helpers::result_output::documents::format_load_execution_json; use crate::bindings::_helpers::result_output::documents::write_execution_json_output; +use crate::bindings::_helpers::result_output::event_writer::ExecutionEventWriter; const LOADER_NODE_KEY: &str = "sqlbuild_loader_node"; const EXECUTION_COMMAND: &str = "sqb load"; @@ -249,15 +254,14 @@ fn project_relations<'py>( let reference_class: Bound<'py, PyAny> = host::entry(py, UserCodeHost::SqlResourceRef)?; let source_kind: Bound<'py, PyAny> = 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( &reference_class, &[("kind", source_kind.clone()), ("name", name)], )?; - let relation: Bound<'py, PyAny> = - call_with(&render, &[("entry", source), ("adapter", adapter.clone())])?; + let relation: String = + render_source_relation(&source, Some(adapter).filter(|value| !value.is_none()))?; relations.set_item(key, relation)?; } Ok(relations.into_any()) @@ -303,13 +307,8 @@ fn prepare<'py>( } else { requested.extract()? }; - let typed: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::TypeCursorOverrides)?, - &[( - "cursor_overrides", - native_field(request, "cursor_overrides")?, - )], - )?; + let typed: Bound<'py, PyAny> = + type_cursor_overrides(&native_field(request, "cursor_overrides")?, &[])?; let cursor_overrides: Bound<'py, PyAny> = if typed.is_truthy()? { typed } else { @@ -395,43 +394,53 @@ fn execute<'py>( ) -> PyResult<(Vec>, f64)> { let py: Python<'py> = request.py(); let started: Instant = Instant::now(); - let event_writer: Bound<'py, PyAny> = - registry::native(py, NativeAttr::ExecutionEventWriter)?.call0()?; + let event_writer: Bound<'py, PyAny> = ExecutionEventWriter::new(py, None) + .and_then(|value| pyo3::Bound::new(py, value)) + .map(pyo3::Bound::into_any)?; let order: Bound<'py, PyDict> = PyDict::new(py); for (index, source) in invocation.selected.iter().enumerate() { order.set_item(native_field(source, "name")?, index + 1)?; } let reporter: Bound<'py, LoadProgressReporter> = - registry::native(py, NativeAttr::LoadProgressReporter)? - .call( - (), - Some(&{ + crate::bindings::_helpers::load::load_progress::LoadProgressReporter::new( + py, + Some( + { let options: Bound<'py, PyDict> = PyDict::new(py); options.set_item("stream", &invocation.stream)?; options.set_item("use_color", invocation.style.use_color)?; options.set_item("source_order", order)?; options.set_item("total_count", invocation.selected.len())?; options - }), - )? - .downcast_into()?; - let connection: Bound<'py, PyAny> = call_with( - ®istry::native(py, NativeAttr::ConnectionProgressReporter)?, - &[ - ("adapter_name", text(py, &preparation.adapter_name)), - ("stream", invocation.stream.clone()), - ( - "blank_line_after_complete", - PyBool::new(py, true).to_owned().into_any(), + } + .clone(), ), - ( - "use_color", - PyBool::new(py, invocation.style.use_color) - .to_owned() - .into_any(), - ), - ], - )?; + ) + .and_then(|value| pyo3::Bound::new(py, value)) + .map(pyo3::Bound::into_any)? + .downcast_into()?; + let connection: Bound<'py, PyAny> = ConnectionProgressReporter::new( + py, + Some(keywords( + py, + &[ + ("adapter_name", text(py, &preparation.adapter_name)), + ("stream", invocation.stream.clone()), + ( + "blank_line_after_complete", + PyBool::new(py, true).to_owned().into_any(), + ), + ( + "use_color", + PyBool::new(py, invocation.style.use_color) + .to_owned() + .into_any(), + ), + ], + )?), + ) + .and_then(|value| pyo3::Bound::new(py, value)) + .map(pyo3::Bound::into_any)?; let relay: Bound<'py, LoadCallbackRelay> = Bound::new( py, LoadCallbackRelay { @@ -524,7 +533,7 @@ fn loader_node_flag(source: &Bound<'_, PyAny>) -> PyResult { /// Run one `sqb load` request. #[pyfunction] #[pyo3(signature = (*, request))] -fn run_load_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { +pub(crate) fn run_load_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { let invocation: Invocation<'_> = invocation(py, request)?; write( &invocation.stream, @@ -557,7 +566,8 @@ 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)?; - registry::native(py, NativeAttr::WriteExecutionHeader)?.call((), Some(&options))?; + write_execution_header(py, Some(options.clone())) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; 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 ea751efde9..37bb5f7858 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/load/load_progress.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/load/load_progress.rs @@ -12,12 +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::build_progress::transient_lines::shared_transient_line_coordinator; +use crate::bindings::_helpers::cli::progress::current_native_progress_projector; 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::fields::field as native_field; -use crate::bindings::_helpers::records::ids::NativeAttr; -use crate::bindings::_helpers::records::registry; const LOADER_NODE_KEY: &str = "sqlbuild_loader_node"; const SPINNER_TICK_SECONDS: f64 = 0.1; @@ -190,7 +190,7 @@ impl LoadProgressReporter { impl LoadProgressReporter { #[new] #[pyo3(signature = (**options))] - fn new(py: Python<'_>, options: Option>) -> PyResult { + pub(crate) fn new(py: Python<'_>, options: Option>) -> PyResult { let arguments: KeywordArguments<'_> = KeywordArguments::read( py, "LoadProgressReporter", @@ -200,10 +200,9 @@ impl LoadProgressReporter { let stream: Bound<'_, PyAny> = arguments.value("stream")?; let source_order: HashMap = arguments.value("source_order")?.extract()?; let total_count: i64 = arguments.value("total_count")?.extract()?; - let lines: Bound<'_, PyAny> = - registry::native(py, NativeAttr::SharedTransientLineCoordinator)?.call0()?; - let projector: Bound<'_, PyAny> = - registry::native(py, NativeAttr::CurrentNativeProgressProjector)?.call0()?; + let lines: Bound<'_, PyAny> = shared_transient_line_coordinator(py) + .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; + let projector: Bound<'_, PyAny> = current_native_progress_projector(py)?; 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/model_config/model_validator.rs b/crates/sqlbuild-python/src/bindings/_helpers/model_config/model_validator.rs index 090d0d39e2..ff64213160 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/model_config/model_validator.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/model_config/model_validator.rs @@ -97,7 +97,10 @@ fn returns_table(file: &Bound<'_, PyAny>) -> PyResult { impl NativeModelValidator { /// The validator for the resources and materializations `inputs` discovered. #[staticmethod] - fn from_inputs(inputs: Bound<'_, PyAny>, microbatch_concurrency: bool) -> PyResult { + pub(crate) fn from_inputs( + inputs: Bound<'_, PyAny>, + microbatch_concurrency: bool, + ) -> PyResult { let names: ResourceNames = discovered_names(&inputs)?; let custom_materializations: HashSet = each(&inputs, "materialization_files", |file| { 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 a3b1ae296a..b31a0e0bd2 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 @@ -186,7 +186,7 @@ fn expand_effective_vars<'py>(raw_values: &Bound<'py, PyDict>) -> PyResult, reads: Vec<(String, String)>) -> PyResult<()> { +pub(crate) fn record_template_reads(py: Python<'_>, reads: Vec<(String, String)>) -> PyResult<()> { record_reads( py, reads 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 05a5ceb660..7653cd894b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/plan_command/plan.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/plan_command/plan.rs @@ -289,7 +289,7 @@ fn write_output( /// Run one `sqb plan` request. #[pyfunction] #[pyo3(signature = (*, request))] -fn run_plan_command(request: &Bound<'_, PyAny>) -> PyResult { +pub(crate) fn run_plan_command(request: &Bound<'_, PyAny>) -> PyResult { let py: Python<'_> = request.py(); let invocation: PlanInvocation<'_> = resolve_invocation(request)?; if !native_field(request, "json_output")?.is_truthy()? { 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 d3265ca2f2..a3be4ae5df 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/plan_command/rules.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/plan_command/rules.rs @@ -276,7 +276,7 @@ fn run_rule_selection( /// List, inspect, execute, or generate guidance for compiler rules. #[pyfunction] #[pyo3(signature = (*, request))] -fn run_rules_command(request: &Bound<'_, PyAny>) -> PyResult { +pub(crate) fn run_rules_command(request: &Bound<'_, PyAny>) -> PyResult { let py: Python<'_> = request.py(); let requested_dir: Bound<'_, PyAny> = native_field(request, "project_dir")?; let project_dir: Bound<'_, PyAny> = if requested_dir.is_none() { 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 39491c9580..c956ad2184 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 @@ -621,7 +621,7 @@ impl<'py> CloneGraph<'py> { /// Return non-view clonable ancestors anchoring selected nodes. #[pyfunction] #[pyo3(signature = (**kwargs))] -fn resolve_clone_boundary<'py>( +pub(crate) fn resolve_clone_boundary<'py>( py: Python<'py>, kwargs: Option>, ) -> PyResult> { @@ -637,7 +637,7 @@ fn resolve_clone_boundary<'py>( /// Return skipped view ancestors rebuilt over cloned boundaries. #[pyfunction] #[pyo3(signature = (**kwargs))] -fn resolve_skipped_view_chain<'py>( +pub(crate) fn resolve_skipped_view_chain<'py>( py: Python<'py>, kwargs: Option>, ) -> PyResult> { 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 ab4eb62687..3b706511a8 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 @@ -31,7 +31,9 @@ fn shared_input_error(carrier: &Carrier<'_>, message: &str) -> PyErr { /// Split `+selector+` marker syntax from the selector core without resolving the core. #[pyfunction] -fn split_selector_expansion<'py>(raw: &Bound<'py, PyString>) -> PyResult> { +pub(crate) fn split_selector_expansion<'py>( + raw: &Bound<'py, PyString>, +) -> PyResult> { let py: Python<'py> = raw.py(); let carrier: Carrier<'py> = Carrier::single(raw)?; let expansion: SelectorExpansion = @@ -79,7 +81,7 @@ fn selectable_model(model: &Bound<'_, PyAny>) -> PyResult { /// One SQLBuild-owned model selector core's model names, and its path selector when translated. #[pyfunction] #[pyo3(signature = (*, project, term))] -fn resolve_sqlbuild_model_selector_names<'py>( +pub(crate) fn resolve_sqlbuild_model_selector_names<'py>( project: &Bound<'py, PyAny>, term: &Bound<'py, PyString>, ) -> PyResult> { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/selectors.rs b/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/selectors.rs index 0be914cf65..efe7476d2b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/selectors.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/planner_selection/selectors.rs @@ -157,7 +157,7 @@ pub(crate) fn select_project_graph<'py>( /// Model, source, seed and function selectors resolved against compiled graph mappings. #[pyfunction] #[pyo3(signature = (**options))] -fn resolve_project_selectors<'py>( +pub(crate) fn resolve_project_selectors<'py>( py: Python<'py>, options: Option>, ) -> PyResult> { @@ -177,7 +177,7 @@ fn resolve_project_selectors<'py>( /// The keys whitespace-separated selector tokens match, without required build resources. #[pyfunction] #[pyo3(signature = (**options))] -fn resolve_project_selector_tokens<'py>( +pub(crate) fn resolve_project_selector_tokens<'py>( py: Python<'py>, options: Option>, ) -> PyResult> { @@ -209,7 +209,7 @@ fn arguments_graph(arguments: &KeywordArguments<'_>) -> PyResult( +pub(crate) fn match_project_selector<'py>( parsed: &Bound<'py, PyAny>, all_keys: &Bound<'py, PyAny>, tag_index: &Bound<'py, PyAny>, 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 388b23df60..c08d164649 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 @@ -155,7 +155,7 @@ pub(crate) fn split_project_unit_test_selectors<'py>( /// Split unit-test selectors from resource selectors for a command that runs unit tests. #[pyfunction] #[pyo3(signature = (*, select, exclude, discovered_inputs))] -fn split_unit_test_selectors<'py>( +pub(crate) fn split_unit_test_selectors<'py>( select: &Bound<'py, PyAny>, exclude: &Bound<'py, PyAny>, discovered_inputs: &Bound<'py, PyAny>, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/presentation_text/style.rs b/crates/sqlbuild-python/src/bindings/_helpers/presentation_text/style.rs index 9a6bb6d0b2..a692d65322 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/presentation_text/style.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/presentation_text/style.rs @@ -23,7 +23,7 @@ impl PythonCliStyle { impl PythonCliStyle { #[new] #[pyo3(signature = (*, use_color))] - fn new(use_color: &Bound<'_, PyAny>) -> PyResult { + pub(crate) fn new(use_color: &Bound<'_, PyAny>) -> PyResult { Ok(Self { use_color: use_color.is_truthy()?, }) 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 cbc1c175ed..183172518e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/query_command/dag.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/query_command/dag.rs @@ -26,7 +26,10 @@ fn array_length(document: &serde_json::Value, key: &str) -> PyResult { /// Run one `sqb dag` invocation. #[pyfunction] #[pyo3(signature = (**options))] -fn run_dag_command<'py>(py: Python<'py>, options: Option>) -> PyResult { +pub(crate) fn run_dag_command<'py>( + py: Python<'py>, + options: Option>, +) -> PyResult { let arguments: KeywordArguments<'py> = KeywordArguments::read( py, "run_dag_command", 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 2f98ba8776..4d8b471f14 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/query_command/query.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/query_command/query.rs @@ -285,7 +285,10 @@ fn render(result: &Bound<'_, PyAny>, output_format: &str, limit: Option) -> /// Execute ad hoc SQL against the active project connection. #[pyfunction] #[pyo3(signature = (**options))] -fn run_query_command(py: Python<'_>, options: Option>) -> PyResult { +pub(crate) fn run_query_command( + py: Python<'_>, + options: Option>, +) -> PyResult { let arguments: KeywordArguments<'_> = KeywordArguments::read( py, "run_query_command", diff --git a/crates/sqlbuild-python/src/bindings/_helpers/records/registry.rs b/crates/sqlbuild-python/src/bindings/_helpers/records/registry.rs index 52b87f24cb..ff4df385eb 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/records/registry.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/records/registry.rs @@ -145,12 +145,3 @@ pub(crate) fn record_named(name: &str) -> PyResult { )) }) } - -/// 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/refactor_command.rs b/crates/sqlbuild-python/src/bindings/_helpers/refactor/refactor_command.rs index 00517fea1d..93aa40f975 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/refactor/refactor_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/refactor/refactor_command.rs @@ -408,7 +408,7 @@ fn finish(py: Python<'_>, command: &Command<'_>, outcome: &Outcome) -> PyResult< /// Plan, verify in a scratch copy, and write one rename or move. #[pyfunction] #[pyo3(signature = (*, request))] -fn run_refactor_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { +pub(crate) fn run_refactor_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { let project_dir: Bound<'_, PyAny> = match native_field(request, "project_dir")? { directory if !directory.is_none() => directory, _ => py.import("pathlib")?.getattr("Path")?.call_method0("cwd")?, 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 fa5273cd2f..e8efe3bb2e 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 @@ -153,7 +153,7 @@ impl TerminalEventIndex { #[pymethods] impl TerminalEventIndex { #[new] - fn new(py: Python<'_>) -> PyResult { + pub(crate) fn new(py: Python<'_>) -> PyResult { Ok(Self { state: Mutex::new(IndexState { index: TerminalIndex::default(), @@ -294,7 +294,7 @@ pub(crate) fn current_terminal_event_index(py: Python<'_>) -> PyResult) -> TerminalEventIndexScope { +pub(crate) fn terminal_event_index_scope(index: Py) -> TerminalEventIndexScope { TerminalEventIndexScope { index, token: Mutex::new(None), 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 93717f90e7..4514422393 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/dsl_headers.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/dsl_headers.rs @@ -134,7 +134,7 @@ fn lint_native_headers<'py>( /// `contents` with header formatting fixes applied, and the faults relocation reported. #[pyfunction] #[pyo3(signature = (*, contents, file_path, config))] -fn format_native_headers<'py>( +pub(crate) fn format_native_headers<'py>( contents: &Bound<'py, PyAny>, file_path: &Bound<'py, PyAny>, config: &Bound<'py, PyAny>, @@ -167,7 +167,7 @@ fn parse_faults<'py>( /// Parse-safe files, and every file's authored newline convention. #[pyfunction] #[pyo3(signature = (*, files, config))] -fn safe_format_files<'py>( +pub(crate) fn safe_format_files<'py>( files: &Bound<'py, PyDict>, config: &Bound<'py, PyAny>, ) -> PyResult<(Bound<'py, PyDict>, Bound<'py, PyDict>)> { @@ -198,7 +198,7 @@ fn safe_format_files<'py>( /// Every rewrite whose header still parses, with the faults of the discarded ones appended. #[pyfunction] #[pyo3(signature = (*, updated, config, faults))] -fn reject_unparseable_header_rewrites<'py>( +pub(crate) fn reject_unparseable_header_rewrites<'py>( updated: &Bound<'py, PyDict>, config: &Bound<'py, PyAny>, faults: &Bound<'py, PyList>, 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 e64fc28b0e..7efa8a6682 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_text.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/lint_text.rs @@ -209,7 +209,7 @@ fn relative_parts(path: &std::path::Path, project_dir: &std::path::Path) -> Opti /// Format the SQL bodies of `files`; return changed texts by path and declined-body faults. #[pyfunction] -fn format_lint_sql_bodies<'py>( +pub(crate) fn format_lint_sql_bodies<'py>( py: Python<'py>, files: Vec<(Bound<'py, PyAny>, String)>, project_dir: PathBuf, 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 2ff8855de1..6cc919eaf7 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 @@ -37,7 +37,10 @@ fn count(label: &str, value: usize) -> SummaryCount { /// Run one `sqb scenario capture` request. #[pyfunction] #[pyo3(signature = (*, request))] -fn run_scenario_capture_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { +pub(crate) fn run_scenario_capture_command( + py: Python<'_>, + request: &Bound<'_, PyAny>, +) -> PyResult { let no_sql_validation: Bound<'_, PyAny> = native_field(request, "no_sql_validation")?; if let Some(error) = sql_analysis_required_error( SqlAnalysisCommand::Capture, 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 6dcc1c8692..ab9aa1935a 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 @@ -312,7 +312,10 @@ fn run_scenarios( /// Run one `sqb scenario test` request. #[pyfunction] #[pyo3(signature = (*, request))] -fn run_scenario_test_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { +pub(crate) fn run_scenario_test_command( + py: Python<'_>, + request: &Bound<'_, PyAny>, +) -> PyResult { let run: TestRun<'_> = prepare(py, request)?; let planning: Bound<'_, PyAny> = write_headers( &run.stream, 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 ca7a6aa86d..6d817d96f2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/scopes/scope_command.rs @@ -331,7 +331,7 @@ fn use_color( /// Load offline compiler facts, run one pure query, and present its result. #[pyfunction] #[pyo3(signature = (*, request, load_scope_index = None, output_stream = None))] -fn run_scope_command( +pub(crate) fn run_scope_command( py: Python<'_>, request: &Bound<'_, PyAny>, load_scope_index: Option>, 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 0d4ce87181..f43a61d2c9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/seed_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/seed_command/command.rs @@ -433,7 +433,7 @@ fn load_seeds<'py>( /// Run one `sqb seed` request. #[pyfunction] #[pyo3(signature = (*, request))] -fn run_seed_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { +pub(crate) fn run_seed_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { let invocation: Bound<'_, PyAny> = resolve_command_invocation(request, &record_class(py, RecordId::SeedInvocation)?)?; let pipeline: Bound<'_, PyAny> = compile_seeds(request, &invocation)?; 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 2197014740..9b5c626bcb 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 @@ -639,7 +639,7 @@ fn complete( /// Run one `sqb test` request. #[pyfunction] #[pyo3(signature = (*, request))] -fn run_test_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { +pub(crate) 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, 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 3be1559613..b5055426ea 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 @@ -117,7 +117,7 @@ fn check_bodies( /// Reject the first authored test block, then scenario, whose CTE names cannot be read. #[pyfunction] -fn validate_authored_sql_test_ctes( +pub(crate) fn validate_authored_sql_test_ctes( py: Python<'_>, test_files: Bound<'_, PyAny>, scenario_files: Bound<'_, PyAny>, 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 0ff0984540..a097a9b844 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/workspace_commands/commands.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/workspace_commands/commands.rs @@ -154,7 +154,10 @@ fn print(py: Python<'_>, text: &str) -> PyResult<()> { /// `sqb skills`: install or update SQLBuild skill files. #[pyfunction] #[pyo3(signature = (**options))] -fn run_skills_command(py: Python<'_>, options: Option>) -> PyResult { +pub(crate) fn run_skills_command( + py: Python<'_>, + options: Option>, +) -> PyResult { let arguments: KeywordArguments<'_> = KeywordArguments::read(py, "run_skills_command", options, SKILLS_OPTIONS)?; let project_dir: Bound<'_, PyAny> = arguments.value("project_dir")?; @@ -180,7 +183,7 @@ fn run_skills_command(py: Python<'_>, options: Option>) -> PyR /// `sqb playground`: create a self-contained playground project. #[pyfunction] -fn run_playground_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { +pub(crate) fn run_playground_command(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult { let project_dir: Bound<'_, PyAny> = native_field(request, "project_dir")?; let base: PathBuf = if project_dir.is_none() { current_dir(py)? diff --git a/tests/unit/src/sqlbuild/lint/test_cli_format.py b/tests/unit/src/sqlbuild/lint/test_cli_format.py index 364dda5bf2..a7f8cf06ea 100644 --- a/tests/unit/src/sqlbuild/lint/test_cli_format.py +++ b/tests/unit/src/sqlbuild/lint/test_cli_format.py @@ -502,7 +502,6 @@ def test_given_test_path_selector_when_formatting_then_work_is_bounded_to_select test_case: FormatSelectionWorkTestCase, tmp_path: Path, capsys: pytest.CaptureFixture[str], - monkeypatch: pytest.MonkeyPatch, ) -> None: (tmp_path / "sqlbuild_project.toml").write_text( PROJECT_TOML + '\n[defaults]\ncontract = "enforced"\n', @@ -548,11 +547,7 @@ def test_given_test_path_selector_when_formatting_then_work_is_bounded_to_select unselected_file: Path = tmp_path / "tests" / "unit" / "customers" / "test_other.sql" unselected_file.parent.mkdir() unselected_file.write_text(fixture_sql, encoding="utf-8") - native_format: Mock = Mock(wraps=native_module.format_lint_sql_bodies) - project_graph: Mock = Mock(side_effect=AssertionError("project graph must not build")) - monkeypatch.setattr(native_module, "format_lint_sql_bodies", native_format) _ = native_module.compile_stage_work_info(reset=True) - monkeypatch.setattr(native_module, "build_project_graph", project_graph) exit_code: int = main( [ @@ -567,10 +562,11 @@ def test_given_test_path_selector_when_formatting_then_work_is_bounded_to_select captured: CaptureResult[str] = capsys.readouterr() assert exit_code == 0 assert f"{test_case.selected_file_count} files checked" in captured.err - assert native_format.call_count == test_case.expected_native_batch_count - assert len(native_format.call_args.args[0]) == test_case.expected_native_file_count - assert native_module.compile_stage_work_info().get("discover_project_inputs", 0) == 0 - assert project_graph.call_count == 0 + stage_work: dict[str, int] = native_module.compile_stage_work_info() + assert stage_work.get("format_lint_batches", 0) == test_case.expected_native_batch_count + assert stage_work.get("format_lint_files", 0) == test_case.expected_native_file_count + assert stage_work.get("discover_project_inputs", 0) == 0 + assert stage_work.get("build_project_graph", 0) == 0 assert all( "CAST(NULL AS VARCHAR)" not in path.read_text(encoding="utf-8") for path in selected_files ) From 8c27605c52d7094182466d8735c98a00e0fc6767 Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Sat, 17 Oct 2026 04:06:35 +0100 Subject: [PATCH 25/28] refactor: read own records through native fields and rebuild them natively in a-l bindings Route non-literal attribute reads in a-l bindings through records::fields::field and replace dataclasses.replace with records::fields::replaced / replaced_with (new: takes a keyword dictionary). Keep the compiled code identity lookup on the extension module, which tests replace to simulate a rebuilt installation. --- .../_helpers/analysis/explicit_references.rs | 2 +- .../_helpers/audit_command/command.rs | 4 +- .../_helpers/build_command/completion.rs | 6 ++- .../_helpers/build_command/defer_clone.rs | 9 ++-- .../_helpers/build_command/execution.rs | 13 +++--- .../_helpers/build_command/invocation.rs | 4 +- .../_helpers/build_command/lifecycle.rs | 8 ++-- .../_helpers/build_command/planning.rs | 2 +- .../_helpers/build_command/policies.rs | 8 ++-- .../build_progress/build_callbacks.rs | 4 +- .../_helpers/build_progress/plan_facts.rs | 2 +- .../_helpers/build_progress/result_text.rs | 9 ++-- .../_helpers/build_progress/terminal.rs | 2 +- .../bindings/_helpers/cache/native_store.rs | 4 +- .../bindings/_helpers/check/check_command.rs | 2 +- .../src/bindings/_helpers/check/check_core.rs | 5 +-- .../src/bindings/_helpers/cli/argv.rs | 2 +- .../src/bindings/_helpers/cli/dispatch.rs | 2 +- .../bindings/_helpers/cli/observability.rs | 6 +-- .../src/bindings/_helpers/cli/progress.rs | 2 +- .../bindings/_helpers/clone/clone_command.rs | 2 +- .../_helpers/clone/clone_execution.rs | 6 +-- .../_helpers/command_runtime/warehouses.rs | 5 ++- .../compile_attachment/model_inputs.rs | 2 +- .../sql_expansion_context.rs | 3 +- .../compile_attachment/storage_policies.rs | 4 +- .../_helpers/compile_attachment/target.rs | 2 +- .../_helpers/compile_command/report.rs | 17 ++++---- .../_helpers/compile_command/reuse_attempt.rs | 8 ++-- .../compile_command/runtime_identity.rs | 2 +- .../_helpers/compile_command/target_writer.rs | 4 +- .../_helpers/compile_driver/analysis.rs | 7 ++-- .../_helpers/compile_driver/analysis_cache.rs | 3 +- .../compile_driver/assembly_analysis.rs | 36 +++++++--------- .../compile_driver/assembly_checks.rs | 20 ++++----- .../compile_driver/assembly_project.rs | 12 +++--- .../_helpers/compile_driver/command.rs | 12 +++--- .../compile_driver/config_validation.rs | 4 +- .../_helpers/compile_driver/deferral.rs | 2 +- .../bindings/_helpers/compile_driver/diff.rs | 2 +- .../_helpers/compile_driver/discovery.rs | 5 +-- .../_helpers/compile_driver/inputs.rs | 36 +++++++--------- .../_helpers/compile_driver/pipeline.rs | 37 +++++++--------- .../compile_driver/pipeline_entries.rs | 4 +- .../_helpers/compile_driver/plan_result.rs | 2 +- .../compile_driver/python_plan_entries.rs | 17 ++++---- .../compile_driver/relation_targets.rs | 2 +- .../compile_driver/target_defaults.rs | 34 +++++++-------- .../_helpers/compile_driver/targets.rs | 4 +- .../compile_outputs/runtime_target.rs | 6 +-- .../_helpers/compiled_project/attachment.rs | 12 +++--- .../_helpers/compiled_project/audit_loop.rs | 2 +- .../compiled_project/compiled_model_view.rs | 10 ++--- .../compiled_project/function_views.rs | 23 +++++----- .../_helpers/compiled_project/loop_classes.rs | 2 +- .../_helpers/compiled_project/model_loop.rs | 17 ++++---- .../compiled_project/model_loop_contracts.rs | 42 ++++++++++--------- .../compiled_project/model_loop_header.rs | 5 ++- .../compiled_project/model_loop_hooks.rs | 26 +++++++----- .../compiled_project/model_loop_host.rs | 13 +++--- .../compiled_project/model_loop_macros.rs | 2 +- .../compiled_project/model_loop_named.rs | 33 +++++++-------- .../compiled_project/model_loop_opt_out.rs | 8 ++-- .../model_loop_python_hooks.rs | 10 ++--- .../compiled_project/model_loop_schema.rs | 23 +++++----- .../compiled_project/model_loop_visibility.rs | 20 ++++----- .../_helpers/compiled_project/project.rs | 28 ++++--------- .../_helpers/compiled_project/project_view.rs | 22 ++++++---- .../_helpers/compiled_project/schema_facts.rs | 29 ++++++------- .../_helpers/compiled_project/seed_views.rs | 29 ++++++------- .../_helpers/compiled_project/source_loop.rs | 7 ++-- .../compiled_project/sql_test_loop.rs | 8 ++-- .../_helpers/compiled_project/values.rs | 13 +++--- .../_helpers/contract_command/command.rs | 4 +- .../_helpers/contract_command/write.rs | 3 +- .../_helpers/contracts/effective_config.rs | 11 +++-- .../_helpers/contracts/spec_resolution.rs | 4 +- .../bindings/_helpers/cost_command/command.rs | 7 ++-- .../bindings/_helpers/cost_command/refresh.rs | 4 +- .../bindings/_helpers/cost_command/sorting.rs | 2 +- .../_helpers/cost_report/degradation.rs | 2 +- .../bindings/_helpers/cost_report/records.rs | 12 +++--- .../_helpers/cost_telemetry/allocation.rs | 3 +- .../_helpers/cost_telemetry/context.rs | 2 +- .../_helpers/cost_telemetry/snowflake.rs | 5 ++- .../_helpers/cost_telemetry/values.rs | 3 +- .../src/bindings/_helpers/cursor/durations.rs | 2 +- .../bindings/_helpers/dag_artifact/rows.rs | 2 +- .../_helpers/dagster_invocation/results.rs | 2 +- .../src/bindings/_helpers/dbt_command/init.rs | 2 +- .../_helpers/dbt_command/init_project.rs | 2 +- .../bindings/_helpers/dbt_events/results.rs | 3 +- .../src/bindings/_helpers/dbt_interop/cli.rs | 4 +- .../_helpers/dbt_interop/pipeline_phases.rs | 20 ++++----- .../_helpers/dbt_interop/plan_output.rs | 12 +++--- .../dbt_interop/reference_resolver.rs | 2 +- .../_helpers/dbt_interop/runtime_config.rs | 2 +- .../_helpers/diagnostics/console_records.rs | 4 +- .../bindings/_helpers/diff_report/facts.rs | 4 +- .../bindings/_helpers/diff_report/query.rs | 12 +++--- .../bindings/_helpers/diff_report/support.rs | 2 +- .../_helpers/diff_report/tolerances.rs | 6 +-- .../_helpers/discovery/hook_entries.rs | 2 +- .../_helpers/discovery/imported_providers.rs | 2 +- .../discovery/imported_python_nodes.rs | 2 +- .../_helpers/discovery/model_records.rs | 3 +- .../_helpers/discovery/project_facts.rs | 10 ++--- .../_helpers/discovery/project_files.rs | 3 +- .../_helpers/discovery/project_validation.rs | 2 +- .../_helpers/event_exporting/dispatcher.rs | 2 +- .../_helpers/execution_history/conversion.rs | 4 +- .../_helpers/fingerprints/storage_sql.rs | 2 +- .../_helpers/freshness/freshness_command.rs | 2 +- .../_helpers/freshness/observation.rs | 5 ++- .../_helpers/freshness/publication.rs | 4 +- .../src/bindings/_helpers/frontier/encoder.rs | 6 +-- .../bindings/_helpers/graph/project_graph.rs | 5 ++- .../_helpers/graph/python_node_graph.rs | 6 +-- .../_helpers/hand_built/compiled_model.rs | 4 +- .../_helpers/janitor/janitor_facts.rs | 23 +++++----- .../bindings/_helpers/janitor/janitor_keys.rs | 2 +- .../_helpers/janitor/janitor_phases.rs | 2 +- .../bindings/_helpers/janitor/janitor_plan.rs | 2 +- .../_helpers/lifecycle_events/envelopes.rs | 10 ++--- .../_helpers/lifecycle_events/validation.rs | 2 +- .../_helpers/lineage/lineage_command.rs | 2 +- .../_helpers/lineage/lineage_index.rs | 2 +- .../_helpers/lineage/project_schema.rs | 4 +- .../bindings/_helpers/load/ingestr_command.rs | 2 +- .../bindings/_helpers/load/load_selection.rs | 12 +++--- 130 files changed, 510 insertions(+), 540 deletions(-) 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 6a2f31c1a2..c3027e71e0 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/analysis/explicit_references.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/analysis/explicit_references.rs @@ -159,7 +159,7 @@ fn python_sql_owners<'py>( let py: Python<'py> = project.py(); let mut owners: Vec> = Vec::new(); for (label, attribute) in NODE_OWNERS { - for node in discovered_inputs.getattr(attribute)?.try_iter()? { + for node in native_field(discovered_inputs, attribute)?.try_iter()? { let node: Bound<'py, PyAny> = node?; let name: String = native_field(&node, "name")?.extract()?; owners.push(PythonSqlOwner::new( 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 c9656e8280..a5278bae9f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/audit_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/audit_command/command.rs @@ -436,7 +436,7 @@ fn audit_payload<'py>( let document: Bound<'py, PyAny> = json.call_method1("loads", (payload,))?; let counts: Bound<'py, PyDict> = PyDict::new(py); for field in ["attempted_count", "written_count", "failed_count"] { - counts.set_item(field, projection.getattr(field)?)?; + counts.set_item(field, native_field(&projection, field)?)?; } document.set_item("audit_result_projection", &counts)?; if native_field(&projection, "degraded")?.is_truthy()? { @@ -444,7 +444,7 @@ fn audit_payload<'py>( let reason: Bound<'py, PyDict> = PyDict::new(py); reason.set_item("reason", PROJECTION_FAILURE)?; for field in ["attempted_count", "written_count", "failed_count"] { - reason.set_item(field, projection.getattr(field)?)?; + reason.set_item(field, native_field(&projection, field)?)?; } let reasons: Bound<'py, PyList> = PyList::empty(py); reasons.append(reason)?; 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 0b05d835a5..96930b3e0d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/completion.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/completion.rs @@ -141,7 +141,8 @@ fn write_timings(context: &BuildContext<'_>, timings: &PhaseTimings) -> PyResult /// Write the phases the tracker measured before an exceptional exit, to stderr without colour. pub(crate) fn write_partial_timings(py: Python<'_>, tracker: &Bound<'_, PyAny>) -> PyResult<()> { let snapshot: Bound<'_, PyAny> = tracker.call_method0("snapshot")?; - let seconds = |name: &str| -> PyResult> { snapshot.getattr(name)?.extract() }; + let seconds = + |name: &str| -> PyResult> { native_field(&snapshot, name)?.extract() }; let timings: PhaseTimings = PhaseTimings { compile_seconds: seconds("compile_seconds")?, planning_seconds: seconds("planning_seconds")?, @@ -385,7 +386,8 @@ pub(crate) fn complete_direct_build<'py>( render_build_cost(&record, &context.stream, context.use_color)?; if context.verbose { let timings: Bound<'py, PyAny> = native_field(result, "timings")?; - let seconds = |name: &str| -> PyResult> { timings.getattr(name)?.extract() }; + let seconds = + |name: &str| -> PyResult> { native_field(&timings, name)?.extract() }; write_timings( context, &PhaseTimings { 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 3fc31ed6a5..9ec6a15e48 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 @@ -16,7 +16,7 @@ use crate::bindings::_helpers::build_command::policies::build_command_error; use crate::bindings::_helpers::build_command::policies::enforce_model_execution_limit_binding; use crate::bindings::_helpers::clone::clone_pipeline::run_clone_pipeline_with_options; use crate::bindings::_helpers::compile_driver::project::build_compiled_project; -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::contracts::spec_resolution::resolve_target_config; use crate::bindings::_helpers::dbt_interop::reference_resolver::resolve_external_sql_reference_resolver; use crate::bindings::_helpers::native_host::executor::ExecutorHost; @@ -28,6 +28,7 @@ use crate::bindings::_helpers::planner_scope::scope::build_planner_scope; use crate::bindings::_helpers::planner_selection::unit_tests::split_unit_test_selectors; use crate::bindings::_helpers::records::construct::{enum_member, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced; use crate::bindings::_helpers::records::ids::RecordId; const VIEW_MATERIALIZATION: &str = "view"; @@ -353,10 +354,8 @@ fn executed_clone<'py>( _: Option<&Bound<'_, PyDict>>| -> PyResult> { let py: Python<'_> = arguments.py(); - let inputs: Bound<'_, PyAny> = imported(py, "dataclasses", "replace")?.call( - (prepared.0.bind(py),), - Some(&keywords(py, &[("on_item", arguments.get_item(0)?)])?), - )?; + let inputs: Bound<'_, PyAny> = + replaced(prepared.0.bind(py), &[("on_item", arguments.get_item(0)?)])?; Ok(prepared .1 .bind(py) 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 3ef9c3b6ea..3ae7608e0c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/execution.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/execution.rs @@ -57,7 +57,7 @@ pub(crate) struct Outcome<'py> { } fn count(value: &Bound<'_, PyAny>, name: &str) -> PyResult { - value.getattr(name)?.len() + native_field(value, name)?.len() } fn validated_date(py: Python<'_>, value: Option>) -> PyResult> { @@ -66,10 +66,7 @@ fn validated_date(py: Python<'_>, value: Option>) -> PyResult = py.import("datetime")?.into_any(); for kind in ["date", "datetime"] { - match datetime - .getattr(kind)? - .call_method1("fromisoformat", (&value,)) - { + match native_field(&datetime, kind)?.call_method1("fromisoformat", (&value,)) { Ok(_) => return Ok(Some(value.extract()?)), Err(error) if error.is_instance_of::(py) => {} Err(error) => return Err(error), @@ -283,7 +280,7 @@ fn verbose_callback<'py>( name: &str, ) -> PyResult> { if context.verbose { - return callbacks.getattr(name); + return native_field(callbacks, name); } Ok(none(context.py())) } @@ -303,7 +300,7 @@ pub(crate) fn execute_build_plan<'py>( let callbacks: &Bound<'py, PyAny> = &preparation.callbacks; let lifecycle: &DirectPythonLifecycle = preparation.lifecycle.get(); let [start_ts, end_ts, start_int, end_int] = &preparation.cursors; - let request = |name: &str| context.request.getattr(name); + let request = |name: &str| native_field(&context.request, name); let runtime: Bound<'py, PyAny> = call_with( &host::entry(py, ExecutorHost::BuildRuntimeParams)?, &[ @@ -493,7 +490,7 @@ fn check_runtime<'py>( fn names_of<'py>(values: &Bound<'py, PyAny>, attribute: &str) -> PyResult>> { values .try_iter()? - .map(|value| value?.getattr(attribute)) + .map(|value| native_field(&value?, attribute)) .collect() } 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 5f6df17539..000e83c016 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/invocation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/invocation.rs @@ -44,11 +44,11 @@ impl<'py> BuildContext<'py> { } pub(crate) fn request_flag(&self, name: &str) -> PyResult { - self.request.getattr(name)?.is_truthy() + native_field(&self.request, name)?.is_truthy() } pub(crate) fn get(&self, name: &str) -> PyResult> { - self.invocation.getattr(name) + native_field(&self.invocation, name) } /// The requested concurrency, or the project setting. 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 6bb425f572..f4ed49db10 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/lifecycle.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/lifecycle.rs @@ -24,6 +24,7 @@ use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::planner_scope::loaders::build_intermediate_source_map; use crate::bindings::_helpers::records::construct::{enum_member, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced_keywords; use crate::bindings::_helpers::records::ids::RecordId; const SUCCESS_STATUS: &str = "success"; @@ -152,7 +153,6 @@ impl DirectPythonLifecycle { .collect::>()?; let functions: Bound<'py, PyAny> = frozenset(py, &functions)?; let ingress: &Bound<'py, PyAny> = self.ingress_loader_names.bind(py); - let replace: Bound<'py, PyAny> = imported(py, "dataclasses", "replace")?; let mut handoff: Vec> = Vec::new(); for loader in loaders { let keeps_all: bool = !ingress.contains(native_field(&loader, "name")?)?; @@ -165,7 +165,7 @@ impl DirectPythonLifecycle { } let changes: Bound<'py, PyDict> = PyDict::new(py); changes.set_item("depends_on", tuple(py, &dependencies)?)?; - handoff.push(replace.call((loader,), Some(&changes))?); + handoff.push(replaced_keywords(&loader, &changes)?); } tuple(py, &handoff) } @@ -241,7 +241,7 @@ fn scheduled_sql_names(plan: &Bound<'_, PyAny>) -> PyResult> { .call1((native_field(plan, "execution_order")?,))?; let mut names: HashSet = HashSet::new(); for kind in ["model_entries", "seed_entries", "source_load_entries"] { - for entry in plan.getattr(kind)?.try_iter()? { + for entry in native_field(plan, kind)?.try_iter()? { let entry: Bound<'_, PyAny> = entry?; if scheduled.contains(native_field(&entry, "key")?)? { names.insert(native_field(&entry, "name")?.extract()?); @@ -355,7 +355,7 @@ fn task_asset_names( ) -> PyResult> { let mut known: HashSet = HashSet::new(); for kind in ["task_functions", "asset_functions"] { - for node in discovered.getattr(kind)?.try_iter()? { + for node in native_field(discovered, kind)?.try_iter()? { known.insert(native_field(&node?, "name")?.extract()?); } } 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 2363611c95..0e9d5993c4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/planning.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/planning.rs @@ -39,7 +39,7 @@ pub(crate) fn run_defer_clone_prephase(context: &BuildContext<'_>) -> PyResult<( fn request_options<'py>(context: &BuildContext<'py>) -> PyResult> { let py: Python<'py> = context.py(); let request: &Bound<'py, PyAny> = &context.request; - let field = |name: &str| request.getattr(name); + let field = |name: &str| native_field(request, name); let should_load_sources: Bound<'py, PyAny> = context.get("should_load_sources")?; let python_selectors: bool = context.request_flag("include_python")? || should_load_sources.is_truthy()?; 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 14e6df5f0b..599fc01b40 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_command/policies.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_command/policies.rs @@ -56,11 +56,11 @@ pub(crate) fn build_command_error(py: Python<'_>, error: BuildCommandError) -> P } fn text(value: &Bound<'_, PyAny>, name: &str) -> PyResult { - value.getattr(name)?.str()?.extract() + native_field(value, name)?.str()?.extract() } fn optional_text(value: &Bound<'_, PyAny>, name: &str) -> PyResult> { - let attribute: Bound<'_, PyAny> = value.getattr(name)?; + let attribute: Bound<'_, PyAny> = native_field(value, name)?; if attribute.is_none() { return Ok(None); } @@ -68,7 +68,7 @@ fn optional_text(value: &Bound<'_, PyAny>, name: &str) -> PyResult, name: &str) -> PyResult { - value.getattr(name)?.is_truthy() + native_field(value, name)?.is_truthy() } fn model_migration(entry: &Bound<'_, PyAny>) -> PyResult { @@ -107,7 +107,7 @@ fn column_migration(entry: &Bound<'_, PyAny>) -> PyResult { } fn entries<'py>(plan: &Bound<'py, PyAny>, name: &str) -> PyResult>> { - plan.getattr(name)?.try_iter()?.collect() + native_field(plan, name)?.try_iter()?.collect() } /// The models a plan executes: every model entry that is not skipped. 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 773e6b0a75..58cafc25bb 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 @@ -31,6 +31,7 @@ use crate::bindings::_helpers::build_progress::terminal::{ }; use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported, within}; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced_keywords; use crate::bindings::_helpers::result_output::event_writer::ExecutionEventWriter; const RUNTIME_LOGGER: &str = "sqlbuild.executor.runtime"; @@ -562,8 +563,7 @@ impl BuildProgressCallbacks { ) { let options: Bound<'_, PyDict> = PyDict::new(py); options.set_item("duration_ms", duration_ms.trunc() as i64)?; - result = - imported(py, "dataclasses", "replace")?.call((node_result,), Some(&options))?; + result = replaced_keywords(node_result, &options)?; } } let written: PyResult<()> = Self::write_node_result(slf, &result, kind); diff --git a/crates/sqlbuild-python/src/bindings/_helpers/build_progress/plan_facts.rs b/crates/sqlbuild-python/src/bindings/_helpers/build_progress/plan_facts.rs index 39fbc20cf0..cc4f34a535 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_progress/plan_facts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_progress/plan_facts.rs @@ -37,7 +37,7 @@ fn row_facts(facts: Option<&ModelLabelFacts>, logical_ddl: Option) -> Mo } fn names(plan: &Bound<'_, PyAny>, field: &str) -> PyResult> { - plan.getattr(field)? + native_field(plan, field)? .try_iter()? .map(|entry| text(&entry?, "name")) .collect() diff --git a/crates/sqlbuild-python/src/bindings/_helpers/build_progress/result_text.rs b/crates/sqlbuild-python/src/bindings/_helpers/build_progress/result_text.rs index 3a2e3db12c..96bf33a0d5 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_progress/result_text.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_progress/result_text.rs @@ -18,11 +18,11 @@ const MEASUREMENT: &str = "measurement"; const FAILED: &str = "failed"; pub(crate) fn text(value: &Bound<'_, PyAny>, name: &str) -> PyResult { - Ok(value.getattr(name)?.str()?.to_string()) + Ok(native_field(value, name)?.str()?.to_string()) } pub(crate) fn optional(value: &Bound<'_, PyAny>, name: &str) -> PyResult> { - let field: Bound<'_, PyAny> = value.getattr(name)?; + let field: Bound<'_, PyAny> = native_field(value, name)?; if field.is_none() { return Ok(None); } @@ -30,15 +30,14 @@ pub(crate) fn optional(value: &Bound<'_, PyAny>, name: &str) -> PyResult, name: &str) -> PyResult> { - value - .getattr(name)? + native_field(value, name)? .try_iter()? .map(|item| Ok(item?.str()?.to_string())) .collect() } fn items<'py>(value: &Bound<'py, PyAny>, name: &str) -> PyResult>> { - value.getattr(name)?.try_iter()?.collect() + native_field(value, name)?.try_iter()?.collect() } fn duration(value: &Bound<'_, PyAny>) -> PyResult> { 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 94ddce2b9d..69a035167b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/build_progress/terminal.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/build_progress/terminal.rs @@ -109,7 +109,7 @@ fn stop_progress_spinners(py: Python<'_>) -> PyResult<()> { if native_field(&thread, "name")?.eq(SPINNER_THREAD_NAME)? && thread.hasattr(STOP_EVENT_ATTRIBUTE)? { - thread.getattr(STOP_EVENT_ATTRIBUTE)?.call_method0("set")?; + native_field(&thread, STOP_EVENT_ATTRIBUTE)?.call_method0("set")?; spinners.push(thread); } } 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 701418fd05..b11fdbce05 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cache/native_store.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cache/native_store.rs @@ -99,8 +99,8 @@ pub(crate) fn compiled_code_identity(py: Python<'_>) -> PyResult { /// `compiled_code_identity`, looked up on the extension module so tests can replace it. pub(crate) fn current_code_identity(py: Python<'_>) -> PyResult { - crate::bindings::_helpers::cache::native_store::compiled_code_identity(py) - .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))? + registry::native(py, NativeAttr::CompiledCodeIdentity)? + .call0()? .extract() } 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 1769d2a6de..5752f51a3c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/check/check_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/check/check_command.rs @@ -277,7 +277,7 @@ fn target_default<'py>( adapter: &Bound<'py, PyAny>, (field, fallback): (&str, &str), ) -> PyResult> { - let value: Bound<'py, PyAny> = project.getattr(field)?; + let value: Bound<'py, PyAny> = native_field(project, field)?; if value.is_none() { adapter.call_method0(fallback) } else { 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 f0bac785b1..94e3060651 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/check/check_core.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/check/check_core.rs @@ -128,7 +128,7 @@ pub(crate) fn record_python_run_state_results<'py>( } let node_by_name: Bound<'py, PyDict> = PyDict::new(py); for attribute in ["task_functions", "asset_functions"] { - for node in discovered_inputs.getattr(attribute)?.try_iter()? { + for node in native_field(discovered_inputs, attribute)?.try_iter()? { let node: Bound<'py, PyAny> = node?; node_by_name.set_item( native_field(&node, "name")?, @@ -292,8 +292,7 @@ fn located<'py>( SEED_KIND => "seed_locations", _ => return Ok(None), }; - let location: Bound<'py, PyAny> = plan_output - .getattr(attribute)? + let location: Bound<'py, PyAny> = native_field(plan_output, attribute)? .call_method1("get", (native_field(reference, "name")?,))?; Ok((!location.is_none()).then_some(location)) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cli/argv.rs b/crates/sqlbuild-python/src/bindings/_helpers/cli/argv.rs index 4f8f227a0a..d1b06b4437 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cli/argv.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cli/argv.rs @@ -214,7 +214,7 @@ fn parse_cli_arguments( Stream::Stdout => "stdout", Stream::Stderr => "stderr", }; - let file: Bound<'_, PyAny> = sys.getattr(name)?; + let file: Bound<'_, PyAny> = native_field(&sys, name)?; if file.is_none() { continue; } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cli/dispatch.rs b/crates/sqlbuild-python/src/bindings/_helpers/cli/dispatch.rs index 3cfd621d70..af30bf6975 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cli/dispatch.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cli/dispatch.rs @@ -469,7 +469,7 @@ pub(crate) fn dispatch_command<'py>( /// The parsed values that select a route, read from the namespace. pub(crate) fn command_facts(args: &Bound<'_, PyAny>) -> PyResult { - let attr = |name: &str| args.getattr(name); + let attr = |name: &str| native_field(args, name); let flag = |name: &str| -> PyResult { attr(name)?.is_truthy() }; let present = |name: &str| -> PyResult { Ok(!attr(name)?.is_none()) }; let command: Option = optional_text(&attr("command")?)?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cli/observability.rs b/crates/sqlbuild-python/src/bindings/_helpers/cli/observability.rs index 6632089e7d..b6b8066a02 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cli/observability.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cli/observability.rs @@ -102,7 +102,7 @@ fn log_exporter_summary(summary: &Bound<'_, PyAny>) -> PyResult<()> { let py: Python<'_> = summary.py(); let mut fields: Vec<(&str, Bound<'_, PyAny>)> = Vec::new(); for name in COUNT_FIELDS.into_iter().chain(SUMMARY_FIELDS) { - fields.push((name, summary.getattr(name)?)); + fields.push((name, native_field(summary, name)?)); } log_debug(py, "Event exporter delivery summary", &fields)?; for exporter in native_field(summary, "per_exporter")?.try_iter()? { @@ -111,10 +111,10 @@ fn log_exporter_summary(summary: &Bound<'_, PyAny>) -> PyResult<()> { let mut fields: Vec<(&str, Bound<'_, PyAny>)> = vec![("exporter_name", native_field(&exporter, "exporter_name")?)]; for name in COUNT_FIELDS { - fields.push((name, counts.getattr(name)?)); + fields.push((name, native_field(&counts, name)?)); } for name in SUMMARY_FIELDS { - fields.push((name, summary.getattr(name)?)); + fields.push((name, native_field(summary, name)?)); } log_debug(py, "Event exporter accounting", &fields)?; } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cli/progress.rs b/crates/sqlbuild-python/src/bindings/_helpers/cli/progress.rs index ab3cc794a2..bbebc961d0 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cli/progress.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cli/progress.rs @@ -119,7 +119,7 @@ fn progress_payload(payload: &Bound<'_, PyAny>) -> PyResult { } fn optional_text(event: &Bound<'_, PyAny>, name: &str) -> PyResult> { - event.getattr(name)?.extract() + native_field(event, name)?.extract() } /// The facts of one lifecycle event that progress projection reads. 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 b37ffffe1f..7efc895ee9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_command.rs @@ -108,7 +108,7 @@ fn lifecycle<'py>(py: Python<'py>, (kind, name): (&str, &str)) -> PyResult) -> PyResult> { let mut names: BTreeSet = BTreeSet::new(); for config in ["project_config", "local_config"] { - for name in native_field(&discovered.getattr(config)?, "targets")?.try_iter()? { + for name in native_field(&native_field(discovered, config)?, "targets")?.try_iter()? { names.insert(name?.extract()?); } } 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 5ef10cec31..f19ce6d00e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_execution.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_execution.rs @@ -37,7 +37,7 @@ const ORIGIN_ENTRY_GROUPS: [&str; 3] = [ fn entries<'py>(pipeline: &Bound<'py, PyAny>, groups: &[&str]) -> PyResult>> { let mut found: Vec> = Vec::new(); for group in groups { - for entry in pipeline.getattr(*group)?.try_iter()? { + for entry in native_field(pipeline, group)?.try_iter()? { found.push(entry?); } } @@ -223,7 +223,7 @@ fn execution_input<'py>( "destination_seed_entries", "destination_function_entries", ] { - fields.push((group, pipeline.getattr(group)?)); + fields.push((group, native_field(pipeline, group)?)); } fields.extend([ ("execution_order", native_field(&plan, "execution_order")?), @@ -279,7 +279,7 @@ fn copy_fingerprints<'py>( "origin_seed_entries", "destination_seed_entries", ] { - fields.push((group, pipeline.getattr(group)?)); + fields.push((group, native_field(pipeline, group)?)); } fields.extend([ ("adapter", invocation.adapter.clone()), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/warehouses.rs b/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/warehouses.rs index 88e4d0e64a..2f6767aa0d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/warehouses.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/warehouses.rs @@ -130,7 +130,10 @@ fn group_value(target: &Bound<'_, PyAny>, group: &Bound<'_, PyAny>) -> PyResult< } else { QUERY_GROUP }; - optional_text(&native_field(target, "warehouses")?.getattr(attribute)?) + optional_text(&native_field( + &native_field(target, "warehouses")?, + attribute, + )?) } /// The active target's authored, unexpanded warehouse for one command group. 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 0b909b6a94..564ae1f492 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 @@ -39,7 +39,7 @@ fn explicit_reference_kinds(py: Python<'_>) -> PyResult> { 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)?)?; + built.set_item(call_kind, native_field(&kinds, member)?)?; } Ok(built) } 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 d29f6ee614..bb19a08ff2 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 @@ -97,7 +97,8 @@ pub(crate) struct SqlExpansionInputs { impl SqlExpansionInputs { /// The inputs a `SqlExpansionContext` record holds. pub(crate) fn from_record(record: &Bound<'_, PyAny>) -> PyResult { - let field = |name: &str| -> PyResult> { Ok(record.getattr(name)?.unbind()) }; + let field = + |name: &str| -> PyResult> { Ok(native_field(record, name)?.unbind()) }; Ok(Self { effective_vars: field("effective_vars")?, loaded_macros: field("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 9ed77d617f..00a44c0b1a 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 @@ -1,7 +1,7 @@ //! The retention and table type a model inherits from its target and materialization defaults. use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; -use pyo3::types::{PyBool, PyString, PyTuple}; +use pyo3::types::{PyBool, PyString, PyStringMethods, PyTuple}; use crate::bindings::_helpers::records::construct::{enum_member, new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; @@ -25,7 +25,7 @@ fn defaults_entry<'py>( return Ok(None); } let name: &Bound<'py, PyString> = materialized.downcast()?; - materialization_defaults.getattr(name).map(Some) + native_field(materialization_defaults, &name.to_cow()?).map(Some) } fn retention<'py>( 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 e1aa18f7a2..9a3d602dae 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/target.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/target.rs @@ -170,7 +170,7 @@ pub(crate) fn build_effective_target_namespace<'py>( if target_config.is_none() { return Ok(py.None().into_bound(py)); } - target_config.getattr(key) + native_field(&target_config, key) }; let values: Bound<'py, PyDict> = PyDict::new(py); values.set_item( 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 1daeafe30f..2248a6f9fa 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_command/report.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_command/report.rs @@ -228,8 +228,8 @@ fn source_texts<'py>(project: &Bound<'py, PyAny>) -> PyResult } } for (collection, file) in [("sources", "source_file"), ("sql_tests", "test_file")] { - for resource in project.getattr(collection)?.try_iter()? { - let file: Bound<'py, PyAny> = resource?.getattr(file)?; + for resource in native_field(project, collection)?.try_iter()? { + let file: Bound<'py, PyAny> = native_field(&resource?, file)?; texts.set_item( native_field(&file, "relative_path")?, native_field(&file, "contents")?, @@ -464,7 +464,7 @@ fn location_json<'py>(location: &Bound<'py, PyAny>) -> PyResult = location.getattr(key)?; + let value: Bound<'py, PyAny> = native_field(location, key)?; if !value.is_none() { payload.set_item(key, value)?; } @@ -495,7 +495,7 @@ fn diagnostic_json<'py>(diagnostic: &Bound<'py, PyAny>) -> PyResult = diagnostic.getattr(key)?; + let value: Bound<'py, PyAny> = native_field(diagnostic, key)?; if !value.is_none() { payload.set_item(key, value)?; } @@ -505,7 +505,7 @@ fn diagnostic_json<'py>(diagnostic: &Bound<'py, PyAny>) -> PyResult = diagnostic.getattr(key)?; + let value: Bound<'py, PyAny> = native_field(diagnostic, key)?; if !value.is_none() { payload.set_item(key, value)?; } @@ -542,7 +542,7 @@ fn diagnostic_json<'py>(diagnostic: &Bound<'py, PyAny>) -> PyResult = diagnostic.getattr(key)?; + let value: Bound<'py, PyAny> = native_field(diagnostic, key)?; if value.is_truthy()? { payload.set_item( key, @@ -873,8 +873,7 @@ fn resources<'py>( collection: &str, resource: impl Fn(&Bound<'py, PyAny>) -> PyResult>, ) -> PyResult> { - let items: Vec> = project - .getattr(collection)? + let items: Vec> = native_field(project, collection)? .try_iter()? .map(|item| resource(&item?)) .collect::>()?; @@ -953,7 +952,7 @@ pub(crate) fn format_compile_json( has_errors = has_errors || native_field(diagnostic, "is_error")?.is_truthy()?; diagnostics.push(diagnostic_json(diagnostic)?); } - let length = |collection: &str| -> PyResult { project.getattr(collection)?.len() }; + let length = |collection: &str| -> PyResult { native_field(project, collection)?.len() }; let summary: Bound<'_, PyDict> = dict( py, vec![ 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 dcf629121d..0f23297cd8 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 @@ -263,7 +263,7 @@ fn reuse_check<'py>( project_dir: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = request.py(); - let flag = |name: &str| -> PyResult { request.getattr(name)?.is_truthy() }; + let flag = |name: &str| -> PyResult { native_field(request, name)?.is_truthy() }; let use_color: bool = !flag("no_color")? && stdout_supports_color(py)?; let check: Bound<'py, PyDict> = PyDict::new(py); check.set_item("project_dir", project_dir)?; @@ -446,7 +446,7 @@ fn reuse_request<'py>(request: &Bound<'py, PyAny>) -> PyResult "skip_contracts", "skip_write", ] { - profiling = profiling || flags.getattr(name)?.is_truthy()?; + profiling = profiling || native_field(&flags, name)?.is_truthy()?; } let mut fields: Vec<(&str, Bound<'py, PyAny>)> = Vec::new(); for name in [ @@ -460,7 +460,7 @@ fn reuse_request<'py>(request: &Bound<'py, PyAny>) -> PyResult "no_cache", "no_sql_validation", ] { - fields.push((name, request.getattr(name)?)); + fields.push((name, native_field(request, name)?)); } fields.push(( "lineage_mode", @@ -471,7 +471,7 @@ fn reuse_request<'py>(request: &Bound<'py, PyAny>) -> PyResult .into_any(), )); for name in ["select", "exclude", "cli_vars", "debug"] { - fields.push((name, request.getattr(name)?)); + fields.push((name, native_field(request, name)?)); } fields.push(("profiling", profiling.into_bound_py_any(py)?)); call_with(&host::entry(py, REUSE_REQUEST)?, &fields) 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 bc53cdfc21..1e3fd55db4 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 @@ -82,7 +82,7 @@ pub(crate) fn invocation_identity<'py>( }; payload.set_item("requested_project_dir", requested)?; for field in REQUEST_CHOICES { - payload.set_item(field, request.getattr(field)?)?; + payload.set_item(field, native_field(request, field)?)?; } payload.set_item("cwd", py.import("os")?.getattr("getcwd")?.call0()?)?; payload.set_item("use_color", use_color)?; 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 83ad0fd0f1..9e86cc4dec 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 @@ -137,7 +137,7 @@ fn function_sql(adapter: &Bound<'_, PyAny>, function: &Bound<'_, PyAny>) -> PyRe "entry_point", "packages", ] { - arguments.push((field, function.getattr(field)?)); + arguments.push((field, native_field(function, field)?)); } let statements: Vec = adapter .getattr("render_create_function")? @@ -218,7 +218,7 @@ fn written_target<'py>( } fn length(value: &Bound<'_, PyAny>, field: &str) -> PyResult { - value.getattr(field)?.len() + native_field(value, field)?.len() } /// Write the planner's compiled output under `target_dir`. 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 bade6855dd..712cbf6f59 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/analysis.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/analysis.rs @@ -24,6 +24,7 @@ use crate::bindings::_helpers::planner_selection::selectors::select_project_grap use crate::bindings::_helpers::planner_selection::unit_tests::reject_unit_test_selectors; use crate::bindings::_helpers::records::construct::{enum_member, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced; use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::rules::constants::RULE_FIX_AVAILABLE_NOTE; use crate::bindings::_helpers::rules::early_sql_lint::EarlySqlLint; @@ -356,9 +357,9 @@ impl<'py> Driver<'_, 'py> { (self.early_lint.getattr("start")?,), Some(&keywords(py, &[("dialect", lint_dialect.clone())])?), )?; - let analysis_selection: Bound<'py, PyAny> = imported(py, "dataclasses", "replace")?.call( - (options.analysis_selection.clone(),), - Some(&keywords(py, &[("on_inputs_ready", on_inputs_ready)])?), + let analysis_selection: Bound<'py, PyAny> = replaced( + &options.analysis_selection.clone(), + &[("on_inputs_ready", on_inputs_ready)], )?; let lineage_mode: Bound<'py, PyAny> = column_lineage_mode( py, 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 49cab95690..c18fa7f098 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 @@ -12,6 +12,7 @@ use crate::bindings::_helpers::compile_driver::compile_timings::record_compile_m use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported}; use crate::bindings::_helpers::planner_identity::json_values::{COMPACT, unicode_sorted_dumps}; use crate::bindings::_helpers::records::construct::new_record_with; +use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; const ANALYSIS_CACHE_VERSION: i64 = 13; @@ -193,7 +194,7 @@ fn inference_profile_payload<'py>( "semantic_known_functions", "semantic_known_types", ] { - payload.set_item(field, profile.getattr(field)?)?; + payload.set_item(field, native_field(profile, field)?)?; } payload.set_item("function_nullability_rules", rules)?; payload.set_item( 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 ab0418770f..b58d9e7358 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 @@ -15,7 +15,7 @@ use crate::bindings::_helpers::compile_driver::assembly::{AssemblyRequest, Assem use crate::bindings::_helpers::compile_driver::compile_timings::{ record_compile_cpu_timing, record_compile_timing, }; -use crate::bindings::_helpers::compile_driver::python_calls::{imported, keywords}; +use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::compile_driver::stage_work::{ EXPRESSION_SHAPES, MODEL_ANALYSIS, count_stage, }; @@ -27,6 +27,7 @@ use crate::bindings::_helpers::project_assembly::declared_relations::{ }; use crate::bindings::_helpers::records::construct::{enum_member, record_class}; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced; use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::semantic_shapes::declarations::{declared_types, function_names}; @@ -168,7 +169,6 @@ fn profile_with_catalog<'py>( column_types: &Bound<'py, PyAny>, ) -> PyResult> { let py: Python<'py> = inputs.py(); - let replace: Bound<'py, PyAny> = imported(py, "dataclasses", "replace")?; let profile: Bound<'py, PyAny> = if inference_profile.is_truthy()? { inference_profile.clone() } else { @@ -192,21 +192,18 @@ fn profile_with_catalog<'py>( Some(native) if Some(native) != name.as_deref() => PyString::new(py, native).into_any(), _ => dialect, }; - let profile: Bound<'py, PyAny> = replace.call( - (profile.clone(),), - Some(&keywords( - py, - &[ - ("semantic_known_functions", known_functions), - ("semantic_known_types", known_types), - ("quoted_identifiers_ignore_case", ignore_case), - ("sql_analysis_dialect", native), - ( - "function_return_types", - function_return_types(inputs, &profile)?.into_any(), - ), - ], - )?), + let profile: Bound<'py, PyAny> = replaced( + &profile.clone(), + &[ + ("semantic_known_functions", known_functions), + ("semantic_known_types", known_types), + ("quoted_identifiers_ignore_case", ignore_case), + ("sql_analysis_dialect", native), + ( + "function_return_types", + function_return_types(inputs, &profile)?.into_any(), + ), + ], )?; let dialect: Bound<'py, PyAny> = native_field(&profile, "sql_analysis_dialect")?; let dialect: String = if dialect.is_truthy()? { @@ -230,10 +227,7 @@ fn profile_with_catalog<'py>( )?, )? .into_any(); - replace.call( - (profile,), - Some(&keywords(py, &[("binding_catalog", catalog)])?), - ) + replaced(&profile, &[("binding_catalog", catalog)]) } /// The profile's function return types with each scalar project function's analysis name. 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 b3a98bc28a..74c1228db6 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,12 +4,13 @@ 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::python_calls::{imported, keywords}; +use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::contracts::spec_resolution::resolve_effective_adapter_name; use crate::bindings::_helpers::records::construct::{ enum_member, new_record, new_record_with, record_class, }; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced; use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::render::declaration_values::declaration_scope_resolver; use crate::bindings::_helpers::render::macro_expansion_result::{ @@ -51,15 +52,12 @@ pub(crate) fn test_model_query_overrides<'py>( if !macro_mocks.is_truthy()? { return Ok(overrides); } - let macro_context: Bound<'py, PyAny> = imported(py, "dataclasses", "replace")?.call( - (model_macro_context(inputs)?,), - Some(&keywords( - py, - &[( - "_enforce_explicit_references", - PyBool::new(py, false).to_owned().into_any(), - )], - )?), + let macro_context: Bound<'py, PyAny> = replaced( + &model_macro_context(inputs)?, + &[( + "_enforce_explicit_references", + PyBool::new(py, false).to_owned().into_any(), + )], )?; let declaration_scope: Bound<'py, PyAny> = native_field(inputs, "declaration_scope")?; let lookup: Bound<'py, PyAny> = if declaration_scope.is_none() { @@ -170,7 +168,7 @@ pub(crate) fn compiled_sql_scenarios<'py>( ], )?; for field in SCENARIO_FIELDS { - fields.set_item(field, scenario_input.getattr(field)?)?; + fields.set_item(field, native_field(&scenario_input, field)?)?; } fields.set_item( "source_path", 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 b29a4cd1b7..45aedec4b9 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 @@ -162,7 +162,7 @@ fn target_field<'py>(inputs: &Bound<'py, PyAny>, field: &str) -> PyResult( fields.set_item("sql_tests", assembled_sql_tests(inputs)?)?; fields.set_item("sql_scenarios", compiled_sql_scenarios(inputs)?)?; for name in ["loader_functions", "hook_functions"] { - fields.set_item(name, discovered.getattr(name)?)?; + fields.set_item(name, native_field(&discovered, name)?)?; } fields.set_item( "enforce_explicit_references", @@ -290,10 +290,10 @@ fn project_fields<'py>( .getattr("enforce_explicit")?, )?; for name in ["sql_hook_files", "materialization_files"] { - fields.set_item(name, discovered.getattr(name)?)?; + fields.set_item(name, native_field(&discovered, name)?)?; } for name in ["public_enums", "public_constants", "loaded_macros"] { - fields.set_item(name, inputs.getattr(name)?)?; + fields.set_item(name, native_field(inputs, name)?)?; } let mut diagnostics: Vec> = inputs .getattr("diagnostics")? @@ -319,12 +319,12 @@ fn compiled_resources<'py>( kind: &ResourceKind<'_>, ) -> PyResult> { let py: Python<'py> = inputs.py(); - let count: usize = inputs.getattr(kind.inputs)?.len()?; + let count: usize = native_field(inputs, kind.inputs)?.len()?; if count == 0 { return Ok(PyTuple::empty(py)); } let owner: Bound<'py, PyAny> = native_field(inputs, "native_project")?; - let facts: Bound<'py, PyAny> = resources.getattr(kind.facts)?; + let facts: Bound<'py, PyAny> = native_field(resources, kind.facts)?; owner.call_method1( kind.record, (PyList::new( 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 0336f47434..6f06f8d174 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/command.rs @@ -29,6 +29,7 @@ use crate::bindings::_helpers::compile_driver::python_calls::{ use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; use crate::bindings::_helpers::records::construct::{enum_member, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced; 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; @@ -65,12 +66,9 @@ pub(crate) fn run_compile_command<'py>( }; let path_class: Bound<'py, PyAny> = imported(py, "pathlib", "Path")?; let effective_request: Bound<'py, PyAny> = if native_field(&request, "project_dir")?.is_none() { - imported(py, "dataclasses", "replace")?.call( - (request.clone(),), - Some(&keywords( - py, - &[("project_dir", path_class.call_method0("cwd")?)], - )?), + replaced( + &request.clone(), + &[("project_dir", path_class.call_method0("cwd")?)], )? } else { request.clone() @@ -400,7 +398,7 @@ fn timings_ms<'py>( ("rule_cache_hits", "rule_cache_hits"), ("rule_cache_misses", "rule_cache_misses"), ] { - timings.set_item(key, analysis.getattr(attribute)?)?; + timings.set_item(key, native_field(analysis, attribute)?)?; } timings.set_item("write_ms", write_ms)?; for (key, value) in timing_milliseconds(detailed_timings) { 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 641a7c4d1e..c39a14c5bd 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 @@ -68,7 +68,7 @@ fn reject_local_sql_analysis_opt_out(config: &ProjectConfiguration<'_, '_>) -> P .getattr("setting_overrides")? .contains(SQL_ANALYSIS_KEY) .map(|overridden| PyBool::new(py, overridden).to_owned().into_any())?, - local_settings.getattr(SQL_ANALYSIS_KEY)?, + native_field(&local_settings, SQL_ANALYSIS_KEY)?, ], )?; validate_local_sql_analysis(py, config.project_dir, flags.extract()?) @@ -82,7 +82,7 @@ fn first_configured_group(config: &Bound<'_, PyAny>) -> PyResult = native_field(&targets.get_item(&name)?, "warehouses")?; for group in WAREHOUSE_GROUPS { - let value: Bound<'_, PyAny> = warehouses.getattr(group)?; + let value: Bound<'_, PyAny> = native_field(&warehouses, group)?; if !value.is_none() { return Ok(Some((name, group.to_owned(), value.extract()?))); } 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 11a6cc5dc5..220cab538d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/deferral.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/deferral.rs @@ -71,7 +71,7 @@ fn deferred_locations<'py>( ) -> PyResult> { let locations: Bound<'py, PyDict> = PyDict::new(project.py()); for attribute in ["models", "seeds"] { - for resource in project.getattr(attribute)?.try_iter()? { + for resource in native_field(project, attribute)?.try_iter()? { let resource: Bound<'py, PyAny> = resource?; let location: Bound<'py, PyAny> = deferred_location(&native_field(&resource, "destination")?, naming)?; 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 7587ca6ddc..234caaeea8 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/diff.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/diff.rs @@ -64,7 +64,7 @@ fn diff_graph<'py>(project: &Bound<'py, PyAny>, keys: &KeyIndexes<'py>) -> PyRes let lineage: Bound<'py, PyAny> = project_lineage_views(py, project)?; let mut graph: DiffGraph = DiffGraph::default(); for attribute in ["models", "sources", "seeds"] { - for resource in project.getattr(attribute)?.try_iter()? { + for resource in native_field(project, attribute)?.try_iter()? { let resource: Bound<'py, PyAny> = resource?; let name: String = native_field(&resource, "name")?.extract()?; graph 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 5b1395612e..e67763d24c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/discovery.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/discovery.rs @@ -398,7 +398,7 @@ fn imported_python_paths(python: &Bound<'_, PyDict>) -> PyResult { let mut groups: Vec> = vec![python.get_item("providers")?]; let nodes: Bound<'_, PyAny> = python.get_item("python_nodes")?; for group in PYTHON_NODE_GROUPS { - groups.push(nodes.getattr(group)?); + groups.push(native_field(&nodes, group)?); } for field in [ "materialization_files", @@ -433,8 +433,7 @@ fn project_inputs<'py>( let tuple_of = |group: &str| -> PyResult> { Ok(PyTuple::new( py, - nodes - .getattr(group)? + native_field(&nodes, group)? .try_iter()? .collect::>>()?, )? 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 66d1b07468..818cce8ad6 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/inputs.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/inputs.rs @@ -11,7 +11,7 @@ use crate::bindings::_helpers::compile_attachment::effective_config::{ use crate::bindings::_helpers::compile_attachment::model_inputs::build_model_inputs; use crate::bindings::_helpers::compile_driver::diagnostic_collector::collect_compile_diagnostics; use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, keywords, within, + KeywordArguments, call_with, keywords, within, }; use crate::bindings::_helpers::compiled_project::attachment::{attach_checks, attach_resources}; use crate::bindings::_helpers::compiled_project::project::NativeCompiledProject; @@ -22,6 +22,7 @@ use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::native_host::user_code::UserCodeHost; use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced; use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::render::declaration_indexes::{ public_declaration_indexes, public_model_schema_index, @@ -326,17 +327,13 @@ fn model_build<'py>( let schemas: Bound<'py, PyAny> = public_model_schema_index(discovered.clone())?.into_any(); let native_project: Bound<'py, PyAny> = Bound::new(py, NativeCompiledProject::default())?.into_any(); - let replace: Bound<'py, PyAny> = imported(py, "dataclasses", "replace")?; - let model_context: Bound<'py, PyAny> = replace.call( - (context,), - Some(&keywords( - py, - &[ - ("public_enums", enums.clone()), - ("public_constants", constants.clone()), - ("public_model_schemas", schemas), - ], - )?), + let model_context: Bound<'py, PyAny> = replaced( + context, + &[ + ("public_enums", enums.clone()), + ("public_constants", constants.clone()), + ("public_model_schemas", schemas), + ], )?; let inputs: Bound<'py, PyAny> = build_model_inputs( py, @@ -362,15 +359,12 @@ fn model_build<'py>( )?), )? .into_any(); - let context: Bound<'py, PyAny> = replace.call( - (context,), - Some(&keywords( - py, - &[ - ("public_enums", enums.clone()), - ("public_constants", constants.clone()), - ], - )?), + let context: Bound<'py, PyAny> = replaced( + context, + &[ + ("public_enums", enums.clone()), + ("public_constants", constants.clone()), + ], )?; Ok(ModelBuild { inputs, 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 f37771ee4a..bddb0345ed 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/pipeline.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/pipeline.rs @@ -13,9 +13,7 @@ use crate::bindings::_helpers::compile_driver::plan_result::{ PlanningInputs, connection_with_hooks, pipeline_result, }; use crate::bindings::_helpers::compile_driver::project::build_compiled_project; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, imported, keywords, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, keywords}; use crate::bindings::_helpers::compile_driver::selection_phases::{ apply_unit_test_selectors, resolve_configured_rule_selection, }; @@ -25,6 +23,7 @@ use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::records::construct::{enum_member, new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; use crate::bindings::_helpers::rules::results::{format_rules_result, rules_progress}; @@ -175,7 +174,7 @@ fn reject_compile_errors( let helps: Bound<'_, PySet> = PySet::empty(py)?; for diagnostic in &errors { let or_one = |name: &str| -> PyResult { - let value: Bound<'_, PyAny> = diagnostic.getattr(name)?; + let value: Bound<'_, PyAny> = native_field(diagnostic, name)?; Ok(if value.is_truthy()? { value.str()?.to_string() } else { @@ -265,8 +264,7 @@ fn run_rules_gate<'py>( RecordId::RulesCacheConfig, &[("enabled", false.into_pyobject(py)?.to_owned().into_any())], )?; - config = imported(py, "dataclasses", "replace")? - .call((config,), Some(&keywords(py, &[("cache", cache)])?))?; + config = replaced(&config, &[("cache", cache)])?; } progress(on_progress, "Evaluating configured rules...")?; let started: f64 = monotonic(py)?; @@ -365,21 +363,18 @@ fn planned<'py>( } let result: Bound<'py, PyAny> = result?; write_plan_capture(&native_field(&result, "plan_output")?)?; - imported(py, "dataclasses", "replace")?.call( - (result,), - Some(&keywords( - py, - &[ - ( - "compile_seconds", - native_field(compiled, "compile_seconds")?, - ), - ( - "planning_seconds", - planning_seconds.into_pyobject(py)?.into_any(), - ), - ], - )?), + replaced( + &result, + &[ + ( + "compile_seconds", + native_field(compiled, "compile_seconds")?, + ), + ( + "planning_seconds", + planning_seconds.into_pyobject(py)?.into_any(), + ), + ], ) } 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 0228b0fdb5..b320ca6221 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 @@ -55,7 +55,7 @@ pub(crate) fn compile_analysis_selection<'py>( let py: Python<'py> = options.py(); let mut has_python_nodes: bool = false; for name in PYTHON_NODE_FUNCTIONS { - if discovered.getattr(name)?.is_truthy()? { + if native_field(discovered, name)?.is_truthy()? { has_python_nodes = true; break; } @@ -133,7 +133,7 @@ pub(crate) fn plan_has_executable_work<'py>( )?; let plan: Bound<'py, PyAny> = arguments.value("plan")?; for name in PLAN_WORK_ENTRIES { - if plan.getattr(name)?.is_truthy()? { + if native_field(&plan, name)?.is_truthy()? { return Ok(true); } } 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 75f90615a8..97309d6123 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 @@ -189,7 +189,7 @@ fn option_fields<'a, 'py>( ) -> PyResult)>> { names .iter() - .map(|name| Ok((*name, options.getattr(*name)?))) + .map(|name| Ok((*name, native_field(options, name)?))) .collect() } 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 db9a00adcb..af3a749e85 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 @@ -2,6 +2,7 @@ use crate::bindings::_helpers::graph::python_lifecycle::build_python_sql_run_lifecycle; use crate::bindings::_helpers::records::construct::{enum_member, new_record_with, record_class}; +use crate::bindings::_helpers::records::fields::replaced; use std::collections::HashMap; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; @@ -12,7 +13,7 @@ use sqlbuild_analysis::python_plan::main::ordered_python_names::ordered_python_n use sqlbuild_analysis::python_plan::main::upstream_closure::upstream_closure; use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, keywords, + KeywordArguments, call_with, imported, }; use crate::bindings::_helpers::graph::python_node_graph::python_node_graph; use crate::bindings::_helpers::native_host::host; @@ -149,12 +150,9 @@ pub(crate) fn build_python_run_plan_outputs<'py>( &run_selection, (&graph, &kinds), )?); - let plan_output: Bound<'py, PyAny> = imported(py, "dataclasses", "replace")?.call( - (plan_output,), - Some(&keywords( - py, - &[("warnings", PyTuple::new(py, warnings)?.into_any())], - )?), + let plan_output: Bound<'py, PyAny> = replaced( + &plan_output, + &[("warnings", PyTuple::new(py, warnings)?.into_any())], )?; let selection: Bound<'py, PyAny> = new_record_with( py, @@ -224,8 +222,7 @@ fn plan_entries<'py>( ("ingress_python_node_names", "PRE_SQL_INGRESS"), ("read_side_python_node_names", "READ_SIDE"), ] { - let selected: Vec = lifecycle - .getattr(attribute)? + let selected: Vec = native_field(lifecycle, attribute)? .try_iter()? .map(|name| name?.extract()) .collect::>()?; @@ -241,7 +238,7 @@ fn plan_entries<'py>( if value.is_none() { Ok(py.None().into_bound(py)) } else { - value.getattr(name) + native_field(value, name) } }; entries.push(new_record_with( 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 92cadbfb21..8a4da6a5da 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 @@ -125,7 +125,7 @@ fn planned_relation_refs<'py>( ("seed_locations", &kinds.seed), ("python_source_entries", &kinds.source), ] { - for name in plan_output.getattr(attribute)?.try_iter()? { + for name in native_field(plan_output, attribute)?.try_iter()? { refs.add(call_with( &reference, &[("kind", kind.clone()), ("name", name?)], 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 8541ceabc0..a09a843ed4 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 @@ -3,13 +3,14 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult, Python}; use pyo3::types::PyTuple; -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::compiled_project::compiled_model_view::NativeCompiledModel; use crate::bindings::_helpers::compiled_project::function_views::NativeCompiledFunction; use crate::bindings::_helpers::compiled_project::records::project_with; use crate::bindings::_helpers::compiled_project::seed_views::NativeCompiledSeed; use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced; use crate::bindings::_helpers::records::ids::RecordId; /// The adapter defaults every compiled location falls back to. @@ -45,7 +46,6 @@ pub(crate) fn apply_target_defaults<'py>( defaults: &TargetDefaults<'py>, ) -> PyResult> { let py: Python<'py> = project.py(); - let replace: Bound<'py, PyAny> = imported(py, "dataclasses", "replace")?; let mut models: Vec> = Vec::new(); for model in native_field(project, "models")?.try_iter()? { let model: Bound<'py, PyAny> = model?; @@ -60,15 +60,12 @@ pub(crate) fn apply_target_defaults<'py>( )? .into_any() } else { - replace.call( - (&model,), - Some(&keywords( - py, - &[( - "destination", - resolved_target(&native_field(&model, "destination")?, defaults)?, - )], - )?), + replaced( + &model, + &[( + "destination", + resolved_target(&native_field(&model, "destination")?, defaults)?, + )], )? }); } @@ -86,15 +83,12 @@ pub(crate) fn apply_target_defaults<'py>( )? .into_any() } else { - replace.call( - (&seed,), - Some(&keywords( - py, - &[( - "destination", - resolved_target(&native_field(&seed, "destination")?, defaults)?, - )], - )?), + replaced( + &seed, + &[( + "destination", + resolved_target(&native_field(&seed, "destination")?, defaults)?, + )], )? }); } 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 26249342fd..dde70832c8 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/targets.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/targets.rs @@ -404,7 +404,7 @@ pub(crate) fn project_targets( let adapter_label: String = adapter_name.str()?.to_cow()?.into_owned(); for (resource_kind, attribute) in [("model", "models"), ("seed", "seeds")] { let mut targets: Vec<(String, Bound<'_, PyAny>)> = Vec::new(); - for resource in project.getattr(attribute)?.try_iter()? { + for resource in native_field(project, attribute)?.try_iter()? { let resource: Bound<'_, PyAny> = resource?; let resource_name: String = native_field(&resource, "name")? .str()? @@ -517,7 +517,7 @@ pub(crate) fn managed_write_schemas( ("Seed", "seeds"), ("Function", "functions"), ] { - for resource in project.getattr(attribute)?.try_iter()? { + for resource in native_field(project, attribute)?.try_iter()? { let resource: Bound<'_, PyAny> = resource?; managed_write_schema( &format!( 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 2c83c893ca..2060ede683 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 @@ -43,7 +43,7 @@ fn path_object<'py>(py: Python<'py>, text: &str) -> PyResult> fn test_key(value: &Bound<'_, PyAny>, name_field: &str) -> PyResult { Ok(( - value.getattr(name_field)?.extract()?, + native_field(value, name_field)?.extract()?, posix(&native_field(value, "source_path")?)?, native_field(value, "block_index")?.extract()?, native_field(value, "case_name")?.extract()?, @@ -153,13 +153,13 @@ pub(crate) fn write_runtime_target( ("model_results", "model_name", &models), ("function_results", "function_name", &functions), ] { - for item in result.getattr(results)?.try_iter()? { + for item in native_field(result, results)?.try_iter()? { let item: Bound<'_, PyAny> = item?; let statements: Vec = sql_statements(&item)?; if statements.is_empty() { continue; } - let name: String = item.getattr(name_field)?.extract()?; + let name: String = native_field(&item, name_field)?.extract()?; if let Some(path) = paths.get(&name) { write_text(&run_dir.div(path)?, &runtime_sql_text(&statements))?; } 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 5d4f09a8f9..b53e51251e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/attachment.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/attachment.rs @@ -40,22 +40,22 @@ const MEASUREMENT_MODE: &str = "MEASUREMENT"; /// `getattr(value, name)` extracted. fn attr<'py, T: pyo3::FromPyObject<'py>>(value: &Bound<'py, PyAny>, name: &str) -> PyResult { - value.getattr(name)?.extract() + native_field(value, name)?.extract() } /// `str(value.)`. fn text(value: &Bound<'_, PyAny>, name: &str) -> PyResult { - Ok(value.getattr(name)?.str()?.to_string()) + Ok(native_field(value, name)?.str()?.to_string()) } /// `value..stem`. fn stem(value: &Bound<'_, PyAny>, name: &str) -> PyResult { - attr(&value.getattr(name)?, "stem") + attr(&native_field(value, name)?, "stem") } /// Each item of the iterable `value.`. fn items<'py>(value: &Bound<'py, PyAny>, name: &str) -> PyResult>> { - value.getattr(name)?.try_iter()?.collect() + native_field(value, name)?.try_iter()?.collect() } /// `{member.value: member for member in enum_class}` of a native string enum. @@ -113,7 +113,7 @@ fn either<'py>( defaults: &Bound<'py, PyAny>, (preferred, fallback): (&str, &str), ) -> PyResult> { - let value: Bound<'py, PyAny> = defaults.getattr(preferred)?; + let value: Bound<'py, PyAny> = native_field(defaults, preferred)?; if value.is_none() { return attr(defaults, fallback); } @@ -558,7 +558,7 @@ fn diagnostic_settings<'py>( native_field(hook, "name")?, text(hook, "relative_path")?.into_bound_py_any(py)?, native_field(hook, "contents")?, - hook.getattr(DESCRIPTION_KEY)?, + native_field(hook, DESCRIPTION_KEY)?, ], ) }) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/audit_loop.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/audit_loop.rs index acca204868..d02cd2cdc6 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/audit_loop.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/audit_loop.rs @@ -472,7 +472,7 @@ impl<'py> AuditScope<'_, 'py> { }), severity, run_scope, - always_run: instance.getattr(ALWAYS_RUN_KEY)?.is_truthy()?, + always_run: native_field(instance, ALWAYS_RUN_KEY)?.is_truthy()?, usages: PyTuple::new(py, usages)?.unbind(), name: native_field(instance, "name")?.extract()?, description: native_field(instance, "description")?.extract()?, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/compiled_model_view.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/compiled_model_view.rs index a06b43dfc4..feba71426f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/compiled_model_view.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/compiled_model_view.rs @@ -178,7 +178,7 @@ pub(crate) fn compiled_record( let analysis: AnalysisRecord = analysis_record(py, input, session, (fallback, standalone_proof))?; let config: &Bound<'_, PyAny> = input.config.bind(py); - let values: Bound<'_, PyDict> = config.getattr(VALUES_ATTRIBUTE)?.downcast_into()?; + let values: Bound<'_, PyDict> = native_field(config, VALUES_ATTRIBUTE)?.downcast_into()?; Ok(CompiledRecord { deps, destination: DestinationFact { @@ -186,8 +186,8 @@ pub(crate) fn compiled_record( schema: text_value(&values, SCHEMA_KEY)?, name: text_value(&values, ALIAS_KEY)?.unwrap_or_else(|| name.to_owned()), qualified_name: None, - logical_schema: config.getattr(LOGICAL_SCHEMA_ATTRIBUTE)?.extract()?, - logical_database: config.getattr(LOGICAL_DATABASE_ATTRIBUTE)?.extract()?, + logical_schema: native_field(config, LOGICAL_SCHEMA_ATTRIBUTE)?.extract()?, + logical_database: native_field(config, LOGICAL_DATABASE_ATTRIBUTE)?.extract()?, }, analysis, macro_deps, @@ -434,7 +434,7 @@ impl NativeCompiledModel { fn model_file_attribute(&self, py: Python<'_>, name: &str) -> PyResult> { self.read(py, |record, py| { - Ok(record.model_file.bind(py).getattr(name)?.unbind()) + Ok(native_field(record.model_file.bind(py), name)?.unbind()) }) } } @@ -831,7 +831,7 @@ pub(crate) fn model_graph_row( ) -> PyResult { model.read(py, |record, py| { let compiled: &CompiledRecord = compiled_of(record)?; - let values: Bound<'_, PyAny> = record.config.bind(py).getattr(VALUES_ATTRIBUTE)?; + let values: Bound<'_, PyAny> = native_field(record.config.bind(py), VALUES_ATTRIBUTE)?; let mut tags: Vec = Vec::new(); if let Some(value) = values.downcast::()?.get_item(TAGS_KEY)? && (value.is_instance_of::() diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/function_views.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/function_views.rs index 7c265a45e9..9049e3ccfa 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/function_views.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/function_views.rs @@ -15,6 +15,7 @@ use crate::bindings::_helpers::compiled_project::model_input_view::{ use crate::bindings::_helpers::compiled_project::model_loop_host::ReferenceRow; use crate::bindings::_helpers::compiled_project::project::NativeCompiledProject; use crate::bindings::_helpers::graph::project_graph::ResourceRow; +use crate::bindings::_helpers::records::fields::field as native_field; const TABLE_FUNCTION_TYPE: &str = "table_fn"; const SCALAR_FUNCTION_TYPE: &str = "udf"; @@ -511,24 +512,22 @@ function_getters!(NativeCompiledFunction, { #[getter] fn relative_path(&self, py: Python<'_>) -> PyResult> { read_function(&self.owner, self.index, py, |function, py| { - Ok(function - .attached - .function_file - .bind(py) - .getattr(FUNCTION_FILE_ATTRIBUTE)? - .unbind()) + Ok(native_field( + function.attached.function_file.bind(py), + FUNCTION_FILE_ATTRIBUTE, + )? + .unbind()) }) } #[getter] fn source_file_path(&self, py: Python<'_>) -> PyResult> { read_function(&self.owner, self.index, py, |function, py| { - Ok(function - .attached - .function_file - .bind(py) - .getattr(FILE_PATH_ATTRIBUTE)? - .unbind()) + Ok(native_field( + function.attached.function_file.bind(py), + FILE_PATH_ATTRIBUTE, + )? + .unbind()) }) } 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 8fa6c0ca60..89e4f36fa5 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 @@ -349,7 +349,7 @@ pub(crate) fn by_attribute<'py>( let keyed: Bound<'py, PyDict> = PyDict::new(items.py()); for item in items.try_iter()? { let item: Bound<'py, PyAny> = item?; - keyed.set_item(item.getattr(attribute)?, item)?; + keyed.set_item(native_field(&item, attribute)?, item)?; } Ok(keyed) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop.rs index c99828098e..01f51cf0ee 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop.rs @@ -543,7 +543,8 @@ impl NativeModelLoop { interpolation, model_file, } = row; - let values: Bound<'py, PyDict> = config.getattr(VALUES_ATTRIBUTE)?.downcast_into()?; + let values: Bound<'py, PyDict> = + native_field(&config, VALUES_ATTRIBUTE)?.downcast_into()?; let schema: Option> = model_schema(context, &values, name)?; let hooked: bool = hook_presence(&values)?; let python_hooks: Vec> = if hooked { @@ -788,12 +789,14 @@ impl NativeModelLoop { .zip(&external) .map(|((kind, name, _, _), error)| (kind.as_str(), name.as_str(), error.is_some())) .collect(); - let unmanaged: Bound<'_, PyAny> = config - .getattr(RETENTION_ATTRIBUTE)? - .getattr(UNMANAGED_ATTRIBUTE)?; - let declared: Bound<'_, PyAny> = config - .getattr(TABLE_TYPE_ATTRIBUTE)? - .getattr(DECLARED_ATTRIBUTE)?; + let unmanaged: Bound<'_, PyAny> = native_field( + &native_field(config, RETENTION_ATTRIBUTE)?, + UNMANAGED_ATTRIBUTE, + )?; + let declared: Bound<'_, PyAny> = native_field( + &native_field(config, TABLE_TYPE_ATTRIBUTE)?, + DECLARED_ATTRIBUTE, + )?; let outcome: Bound<'_, PyAny> = self.validator.bind(py).call_method1( VALIDATE_METHOD, ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_contracts.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_contracts.rs index 14a928a6a5..b82fe8f7bf 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_contracts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_contracts.rs @@ -8,6 +8,7 @@ use sqlbuild_model_config::header_metadata::models::DynamicFamily; use crate::bindings::_helpers::compiled_project::model_loop::LoopContext; use crate::bindings::_helpers::compiled_project::model_loop_schema::DeclaredColumns; +use crate::bindings::_helpers::records::fields::field as native_field; const CASES_ATTRIBUTE: &str = "cases"; const RELATIVE_PATH_ATTRIBUTE: &str = "relative_path"; @@ -81,22 +82,25 @@ pub(crate) fn factory_audits<'py>( let location: Bound<'py, PyAny> = keywords( &context.contracts.source_location, &[ - (PATH_FIELD, factory.getattr(RELATIVE_PATH_ATTRIBUTE)?), - (LINE_ATTRIBUTE, factory.getattr(LINE_ATTRIBUTE)?), + (PATH_FIELD, native_field(&factory, RELATIVE_PATH_ATTRIBUTE)?), + (LINE_ATTRIBUTE, native_field(&factory, LINE_ATTRIBUTE)?), (COLUMN_FIELD, 1_u8.into_pyobject_any(py)?), ], )?; - for case in factory.getattr(CASES_ATTRIBUTE)?.try_iter()? { + for case in native_field(&factory, CASES_ATTRIBUTE)?.try_iter()? { let case: Bound<'py, PyAny> = case?; let fields: Bound<'py, PyDict> = PyDict::new(py); - fields.set_item(DEFINITION_NAME_FIELD, case.getattr(DEFINITION_ATTRIBUTE)?)?; + fields.set_item( + DEFINITION_NAME_FIELD, + native_field(&case, DEFINITION_ATTRIBUTE)?, + )?; fields.set_item( ARGUMENTS_FIELD, py.get_type::() - .call1((case.getattr(ARGUMENTS_FIELD)?,))?, + .call1((native_field(&case, ARGUMENTS_FIELD)?,))?, )?; for field in COPIED_CASE_FIELDS { - fields.set_item(field, case.getattr(field)?)?; + fields.set_item(field, native_field(&case, field)?)?; } fields.set_item(LOCATION_FIELD, &location)?; let audit: Bound<'py, PyAny> = context @@ -112,11 +116,11 @@ pub(crate) fn factory_audits<'py>( /// `audit.name or audit.definition_name`. pub(crate) fn audit_identity(audit: &Bound<'_, PyAny>) -> PyResult { - let name: Bound<'_, PyAny> = audit.getattr(NAME_FIELD)?; + let name: Bound<'_, PyAny> = native_field(audit, NAME_FIELD)?; if name.is_truthy()? { return name.extract(); } - audit.getattr(DEFINITION_NAME_FIELD)?.extract() + native_field(audit, DEFINITION_NAME_FIELD)?.extract() } /// The parsed families as `SchemaDynamicColumnFamily` objects. @@ -182,8 +186,7 @@ pub(crate) fn enum_columns<'py>( continue; }; resolved.set_item(&fact.name, &declaration)?; - let mut audits: Vec> = column - .getattr(AUDITS_FIELD)? + let mut audits: Vec> = native_field(&column, AUDITS_FIELD)? .try_iter()? .collect::>()?; if enforced { @@ -201,11 +204,11 @@ pub(crate) fn enum_columns<'py>( .push(ACCEPTED_VALUES_AUDIT.to_owned()); } } - let scalar_type: Bound<'py, PyAny> = declaration.getattr(SCALAR_TYPE_ATTRIBUTE)?; + let scalar_type: Bound<'py, PyAny> = native_field(&declaration, SCALAR_TYPE_ATTRIBUTE)?; fact.data_type = scalar_type.extract()?; let fields: Bound<'py, PyDict> = PyDict::new(py); for field in KEPT_COLUMN_FIELDS { - fields.set_item(field, column.getattr(field)?)?; + fields.set_item(field, native_field(&column, field)?)?; } fields.set_item(TYPE_FIELD, scalar_type)?; fields.set_item(AUDITS_FIELD, PyTuple::new(py, audits)?)?; @@ -228,8 +231,8 @@ fn accepted_values<'py>( ) -> PyResult> { let py: Python<'py> = declaration.py(); let mut members: Vec> = Vec::new(); - for member in declaration.getattr(MEMBERS_FIELD)?.try_iter()? { - members.push(member?.getattr(VALUE_FIELD)?); + for member in native_field(declaration, MEMBERS_FIELD)?.try_iter()? { + members.push(native_field(&member?, VALUE_FIELD)?); } let arguments: Bound<'py, PyDict> = PyDict::new(py); arguments.set_item(VALUES_ARGUMENT, PyTuple::new(py, members)?)?; @@ -251,12 +254,11 @@ fn generated_usages<'py>( consumer: &Bound<'py, PyAny>, resolved: &Bound<'py, PyDict>, ) -> PyResult>> { - let visibility: Bound<'py, PyDict> = declarations - .getattr(ENUM_VISIBILITY_ATTRIBUTE)? - .downcast_into()?; + let visibility: Bound<'py, PyDict> = + native_field(declarations, ENUM_VISIBILITY_ATTRIBUTE)?.downcast_into()?; let mut usages: Vec> = Vec::new(); for declaration in resolved.values() { - let Some(records) = visibility.get_item(declaration.getattr(NAME_FIELD)?)? else { + let Some(records) = visibility.get_item(native_field(&declaration, NAME_FIELD)?)? else { continue; }; for record in records.try_iter()? { @@ -265,9 +267,9 @@ fn generated_usages<'py>( &context.classes.usage_record, &[ (CONSUMER_FIELD, consumer.clone()), - (DECLARATION_FIELD, record.getattr(DECLARATION_FIELD)?), + (DECLARATION_FIELD, native_field(&record, DECLARATION_FIELD)?), (KIND_FIELD, context.classes.generated_usage.clone()), - (THROUGH_FIELD, record.getattr(THROUGH_FIELD)?), + (THROUGH_FIELD, native_field(&record, THROUGH_FIELD)?), ], )?); } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_header.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_header.rs index 57fad6509a..75fbcd9740 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_header.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_header.rs @@ -28,6 +28,7 @@ use crate::bindings::_helpers::model_config::authored_nodes::PyNode; use crate::bindings::_helpers::model_config::column_migrations::column_migration_error; use crate::bindings::_helpers::model_config::config_errors::native_config_error; use crate::bindings::_helpers::model_config::header_metadata::{audit_tuple, column_tuple}; +use crate::bindings::_helpers::records::fields::field as native_field; const DESCRIPTION_KEY: &str = "description"; const COLUMNS_KEY: &str = "columns"; @@ -73,7 +74,7 @@ pub(crate) fn header_schema<'py>( source: &HeaderSource<'_, 'py>, ) -> PyResult> { let description: Option = match (description(context, source)?, source.schema) { - (None, Some(schema)) => schema.declaration.getattr(DESCRIPTION_KEY)?.extract()?, + (None, Some(schema)) => native_field(&schema.declaration, DESCRIPTION_KEY)?.extract()?, (description, _) => description, }; let columns: Option> = present(source.values, COLUMNS_KEY)?; @@ -154,7 +155,7 @@ pub(crate) fn header_schema<'py>( direct_audits.iter().chain(generated).collect::>(), )?; let families: Vec> = dynamic_family_objects(py, context, &families)?; - let enums: Bound<'py, PyDict> = source.visible.0.getattr(ENUMS_FIELD)?.downcast_into()?; + let enums: Bound<'py, PyDict> = native_field(source.visible.0, ENUMS_FIELD)?.downcast_into()?; let enums: Bound<'py, PyDict> = match source.schema { Some(schema) => { let merged: Bound<'py, PyDict> = enums.copy()?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_hooks.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_hooks.rs index 8fdd710adf..2f27b2dd4e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_hooks.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_hooks.rs @@ -36,6 +36,7 @@ use crate::bindings::_helpers::compiled_project::model_loop_named::{ SQL_HOOK_DECLARATION, named_usages, }; use crate::bindings::_helpers::compiled_project::model_loop_visibility::ModelVisibility; +use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::render::declaration_expansion::{ DeclarationExpansion, ExpandedDeclarations, ExpansionClasses, ExpansionConsumer, expand_declarations, @@ -229,7 +230,7 @@ impl<'py> HookRun<'_, '_, 'py> { ) -> PyResult> { let start: usize = self.references.borrow().len(); let statement: String = self - .value(&entry.getattr(STATEMENT_ATTRIBUTE)?, scope, position)? + .value(&native_field(entry, STATEMENT_ATTRIBUTE)?, scope, position)? .str()? .to_str()? .to_owned(); @@ -243,7 +244,7 @@ impl<'py> HookRun<'_, '_, 'py> { KWARGS_ATTRIBUTE, DESCRIPTION_ATTRIBUTE, ] { - fields.set_item(field, entry.getattr(field)?)?; + fields.set_item(field, native_field(entry, field)?)?; } fields.set_item(READS_ATTRIBUTE, self.reads_since(start)?)?; self.context.classes.sql_hook_entry.call((), Some(&fields)) @@ -256,7 +257,7 @@ impl<'py> HookRun<'_, '_, 'py> { position: HookPosition<'_>, ) -> PyResult> { let py: Python<'py> = entry.py(); - let name: String = entry.getattr(NAME_ATTRIBUTE)?.extract()?; + let name: String = native_field(entry, NAME_ATTRIBUTE)?.extract()?; let label: String = named_label(&name, position); let Some(definition) = self.classes().definitions.get_item(&name)? else { let mut known: Vec = self @@ -285,9 +286,10 @@ impl<'py> HookRun<'_, '_, 'py> { &scope.consumer, (self.model.name, self.model.relative_path), )?); - let kwargs: Bound<'py, PyDict> = entry.getattr(KWARGS_ATTRIBUTE)?.downcast_into()?; - let body: String = definition.getattr(SQL_BODY_ATTRIBUTE)?.extract()?; - let definition_path: Bound<'py, PyAny> = definition.getattr(RELATIVE_PATH_ATTRIBUTE)?; + let kwargs: Bound<'py, PyDict> = native_field(entry, KWARGS_ATTRIBUTE)?.downcast_into()?; + let body: String = native_field(&definition, SQL_BODY_ATTRIBUTE)?.extract()?; + let definition_path: Bound<'py, PyAny> = + native_field(&definition, RELATIVE_PATH_ATTRIBUTE)?; let owner: String = format!("{} {label}", scope.file_path); let definition_label: String = format!("SQL hook '{name}' in {}", definition_path.str()?.to_str()?); @@ -308,8 +310,7 @@ impl<'py> HookRun<'_, '_, 'py> { compile_error(self.context.classes, &message, None) })?; let start: usize = self.references.borrow().len(); - let hook_path: String = definition - .getattr(FILE_PATH_ATTRIBUTE)? + let hook_path: String = native_field(&definition, FILE_PATH_ATTRIBUTE)? .str()? .to_str()? .to_owned(); @@ -330,7 +331,7 @@ impl<'py> HookRun<'_, '_, 'py> { fields.set_item(KWARGS_ATTRIBUTE, kwargs.copy()?)?; fields.set_item( DESCRIPTION_ATTRIBUTE, - definition.getattr(DESCRIPTION_ATTRIBUTE)?, + native_field(&definition, DESCRIPTION_ATTRIBUTE)?, )?; fields.set_item(READS_ATTRIBUTE, self.reads_since(start)?)?; self.context.classes.sql_hook_entry.call((), Some(&fields)) @@ -396,7 +397,10 @@ impl<'py> HookRun<'_, '_, 'py> { } else { let fields: Bound<'py, PyDict> = PyDict::new(py); for field in UNSCOPED_FIELDS { - fields.set_item(field, self.model.visible.declarations.getattr(field)?)?; + fields.set_item( + field, + native_field(&self.model.visible.declarations, field)?, + )?; } self.context .classes @@ -558,7 +562,7 @@ fn reject_hook_intrinsics( if !entry.is_instance(&context.classes.sql_hook_entry)? { continue; } - let statement: String = entry.getattr(STATEMENT_ATTRIBUTE)?.extract()?; + let statement: String = native_field(&entry, STATEMENT_ATTRIBUTE)?.extract()?; if let IntrinsicCheck::Rejected(message) = intrinsic_free(&statement, &model_loop.reserved_markers, &hook_context) { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_host.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_host.rs index cb761c9cee..478af8e829 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_host.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_host.rs @@ -22,6 +22,7 @@ use crate::bindings::_helpers::compiled_project::project::{ DeclarationPy, config_values, declarations, }; use crate::bindings::_helpers::discovery::model_file_record::NativeModelFile; +use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::render::declaration_expansion::{ ExpandedDeclarations, ScannedReference, span_rows, }; @@ -76,7 +77,7 @@ impl ModelLoopRow { let (query_sql, contents) = record.texts(py); Ok(Self { paths: ModelPaths { - name: file_path.getattr(STEM_ATTRIBUTE)?.extract()?, + name: native_field(file_path, STEM_ATTRIBUTE)?.extract()?, posix_path: relative_path.call_method0(AS_POSIX_METHOD)?.extract()?, relative_path: relative_path.str()?.to_str()?.to_owned(), file_path: file_path.str()?.to_str()?.to_owned(), @@ -158,9 +159,9 @@ pub(crate) fn finished_record<'py>( None => Vec::new(), }; let local_enums: Bound<'py, PyTuple> = - dict_values(&inputs.visible.getattr(LOCAL_ENUMS_ATTRIBUTE)?)?; + dict_values(&native_field(inputs.visible, LOCAL_ENUMS_ATTRIBUTE)?)?; let local_constants: Bound<'py, PyTuple> = - dict_values(&inputs.visible.getattr(LOCAL_CONSTANTS_ATTRIBUTE)?)?; + dict_values(&native_field(inputs.visible, LOCAL_CONSTANTS_ATTRIBUTE)?)?; let rows_arguments: Bound<'py, PyDict> = PyDict::new(py); rows_arguments.set_item(ENUMS_ARGUMENT, &local_enums)?; rows_arguments.set_item(CONSTANTS_ARGUMENT, &local_constants)?; @@ -188,7 +189,7 @@ pub(crate) fn finished_record<'py>( .map(|item| item.into_bound(py)) .collect(), )? { - names.push(dependency.getattr(NAME_ATTRIBUTE)?.extract()?); + names.push(native_field(&dependency, NAME_ATTRIBUTE)?.extract()?); } ( macros.sql, @@ -275,8 +276,8 @@ pub(crate) fn finished_record<'py>( config: ModelConfigFacts { values, encoding_error, - header_keys: finished.config.getattr(HEADER_KEYS_ATTRIBUTE)?.extract()?, - layer_schema: finished.config.getattr(LAYER_SCHEMA_ATTRIBUTE)?.extract()?, + header_keys: native_field(&finished.config, HEADER_KEYS_ATTRIBUTE)?.extract()?, + layer_schema: native_field(&finished.config, LAYER_SCHEMA_ATTRIBUTE)?.extract()?, contract: text_value(&stripped, CONTRACT_KEY)?, materialized: materialization(&stripped)?, incremental_mode: text_value(&stripped, INCREMENTAL_MODE_KEY)?, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_macros.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_macros.rs index 1c48783a98..3f3a0bb971 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_macros.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_macros.rs @@ -80,7 +80,7 @@ pub(crate) fn expanded_macros<'py>( let declarations: Bound<'py, PyAny> = if classes.scoped { let fields: Bound<'py, PyDict> = PyDict::new(py); for field in CONTEXT_FIELDS { - fields.set_item(field, visible.declarations.getattr(field)?)?; + fields.set_item(field, native_field(&visible.declarations, field)?)?; } fields.set_item("consumer", &visible.consumer)?; classes.declaration_context.call((), Some(&fields))? diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_named.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_named.rs index 6a237a47f2..88fdb63008 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_named.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_named.rs @@ -11,6 +11,7 @@ use sqlbuild_scopes::scope_index::models::{ }; use crate::bindings::_helpers::compiled_project::model_loop::LoopContext; +use crate::bindings::_helpers::records::fields::field as native_field; const NAME_FIELD: &str = "name"; const PATH_FIELD: &str = "path"; @@ -105,7 +106,7 @@ pub(crate) fn resource_named_usages<'py>( } let fields: Bound<'py, PyDict> = PyDict::new(py); fields.set_item(CONSUMER_FIELD, consumer)?; - fields.set_item(DECLARATION_FIELD, record.getattr(IDENTITY_FIELD)?)?; + fields.set_item(DECLARATION_FIELD, native_field(&record, IDENTITY_FIELD)?)?; Ok(vec![context.classes.usage_record.call((), Some(&fields))?]) } @@ -145,22 +146,22 @@ pub(crate) fn scope_path_message(declared: &DeclaredScope, label: &str) -> Strin /// The scope facts of a Python `DeclarationRecord`. fn declared_scope(record: &Bound<'_, PyAny>) -> PyResult { - let scope: String = record - .getattr(SCOPE_FIELD)? - .getattr(VALUE_FIELD)? - .extract()?; - let owner: Bound<'_, PyAny> = record.getattr(IDENTITY_FIELD)?.getattr(OWNER_FIELD)?; + let scope: String = + native_field(&native_field(record, SCOPE_FIELD)?, VALUE_FIELD)?.extract()?; + let owner: Bound<'_, PyAny> = + native_field(&native_field(record, IDENTITY_FIELD)?, OWNER_FIELD)?; let owner: Option = if owner.is_none() { None } else { - let kind: String = owner.getattr(KIND_FIELD)?.getattr(VALUE_FIELD)?.extract()?; - let name: String = owner.getattr(NAME_FIELD)?.extract()?; + let kind: String = + native_field(&native_field(&owner, KIND_FIELD)?, VALUE_FIELD)?.extract()?; + let name: String = native_field(&owner, NAME_FIELD)?.extract()?; ResourceKind::parse(&kind).map(|kind| ResourceIdentity { kind, name }) }; Ok(DeclaredScope { scope: ScopeKind::parse(&scope).unwrap_or(ScopeKind::Global), owner, - owning_path: record.getattr(OWNING_PATH_FIELD)?.extract()?, + owning_path: native_field(record, OWNING_PATH_FIELD)?.extract()?, }) } @@ -179,21 +180,17 @@ fn report_inaccessible( .and_then(Path::to_str) .filter(|parent| !parent.is_empty()) .unwrap_or(CURRENT_FOLDER); - let owning_path: Bound<'_, PyAny> = record.getattr(OWNING_PATH_FIELD)?; + let owning_path: Bound<'_, PyAny> = native_field(record, OWNING_PATH_FIELD)?; let owner: String = if owning_path.is_truthy()? { owning_path.str()?.to_string() } else { - record - .getattr(OWNERSHIP_ROOT_FIELD)? - .getattr(PATH_FIELD)? + native_field(&native_field(record, OWNERSHIP_ROOT_FIELD)?, PATH_FIELD)? .str()? .to_string() }; - let record_path: String = record.getattr(PATH_FIELD)?.str()?.to_string(); - let scope: String = record - .getattr(SCOPE_FIELD)? - .getattr(VALUE_FIELD)? - .extract()?; + let record_path: String = native_field(record, PATH_FIELD)?.str()?.to_string(); + let scope: String = + native_field(&native_field(record, SCOPE_FIELD)?, VALUE_FIELD)?.extract()?; let (label_text, role) = (kind.label, kind.role); let fields: Bound<'_, PyDict> = PyDict::new(py); fields.set_item("phase", "compile")?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_opt_out.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_opt_out.rs index dcd90a2846..afde98b0d6 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_opt_out.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_opt_out.rs @@ -14,6 +14,7 @@ use crate::bindings::_helpers::compiled_project::model_loop::{ }; use crate::bindings::_helpers::compiled_project::model_loop_inputs::{HOOK_KEYS, present}; use crate::bindings::_helpers::project_assembly::resources::{syntax_error_message, syntax_valid}; +use crate::bindings::_helpers::records::fields::field as native_field; const OPT_OUT_KEYS: [&str; 2] = ["sql_analysis", "sql_validation"]; const CURSOR_TYPE_KEY: &str = "cursor_type"; @@ -150,7 +151,7 @@ fn hook_statements( return Ok(vec![text.extract()?]); } if value.is_instance(&context.classes.sql_hook_entry)? { - return hook_statements(context, &value.getattr(STATEMENT_ATTRIBUTE)?); + return hook_statements(context, &native_field(value, STATEMENT_ATTRIBUTE)?); } let mut statements: Vec = Vec::new(); if value.is_instance_of::() || value.is_instance_of::() { @@ -181,14 +182,15 @@ fn location<'py>( model: &OptOutModel<'_, 'py>, ) -> PyResult> { let py: Python<'py> = model.config.py(); - let header_keys: Vec = model.config.getattr(HEADER_KEYS_ATTRIBUTE)?.extract()?; + let header_keys: Vec = native_field(model.config, HEADER_KEYS_ATTRIBUTE)?.extract()?; let (path, position): (&str, TextPosition) = if header_keys .iter() .any(|key| OPT_OUT_KEYS.contains(&key.as_str())) { (model.relative_path, opt_out_position(model.contents, 0)) } else { - let path_key: Option = model.config.getattr(PATH_DEFAULT_ATTRIBUTE)?.extract()?; + let path_key: Option = + native_field(model.config, PATH_DEFAULT_ATTRIBUTE)?.extract()?; let config_path: &Path = Path::new(&model_loop.project_config_path); let config_text: String = if config_path.is_file() { std::fs::read_to_string(config_path)? diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_python_hooks.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_python_hooks.rs index a3ccac90e9..d0fa86a4c2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_python_hooks.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_python_hooks.rs @@ -11,6 +11,7 @@ use crate::bindings::_helpers::compiled_project::model_loop::{LoopContext, compi use crate::bindings::_helpers::compiled_project::model_loop_named::{ PYTHON_HOOK_DECLARATION, named_usages, }; +use crate::bindings::_helpers::records::fields::field as native_field; /// Hook keys in the sorted order `validate_python_hook_config` reads them. const SORTED_HOOK_KEYS: [&str; 2] = ["post_hooks", "pre_hooks"]; @@ -67,7 +68,7 @@ impl HookSignatures { let entries: Vec<(&str, usize, Bound<'py, PyAny>)> = python_entries(context, values)?; let functions: &Bound<'py, PyDict> = &context.classes.hooks.functions; for (hook_key, index, entry) in &entries { - let name: String = entry.getattr(NAME_FIELD)?.extract()?; + let name: String = native_field(entry, NAME_FIELD)?.extract()?; let site: HookSite<'_> = HookSite { model_name, hook_key, @@ -88,8 +89,7 @@ impl HookSignatures { ); return Err(compile_error(context.classes, &message, None)); }; - let kwargs: Vec = entry - .getattr(KWARGS_FIELD)? + let kwargs: Vec = native_field(entry, KWARGS_FIELD)? .downcast_into::()? .keys() .extract()?; @@ -102,7 +102,7 @@ impl HookSignatures { } let mut usages: Vec> = Vec::new(); for (_, _, entry) in &entries { - let name: String = entry.getattr(NAME_FIELD)?.extract()?; + let name: String = native_field(entry, NAME_FIELD)?.extract()?; usages.extend(named_usages( context, (&PYTHON_HOOK_DECLARATION, &name), @@ -127,7 +127,7 @@ impl HookSignatures { .classes .hooks .signature - .call1((function.getattr(FUNCTION_FIELD)?,))? + .call1((native_field(function, FUNCTION_FIELD)?,))? .extract()?; self.by_name .borrow_mut() diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_schema.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_schema.rs index 7c6d83eb43..a8d6362566 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_schema.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_schema.rs @@ -7,6 +7,7 @@ use sqlbuild_analysis::compiled_project::models::SchemaColumnFact; use crate::bindings::_helpers::compiled_project::model_loop::{LoopContext, compile_error}; use crate::bindings::_helpers::compiled_project::model_loop_inputs::present; use crate::bindings::_helpers::compiled_project::schema_facts::python_column_fact; +use crate::bindings::_helpers::records::fields::field as native_field; const MODEL_SCHEMA_KEY: &str = "model_schema"; const NAME_FIELD: &str = "name"; @@ -78,7 +79,7 @@ pub(crate) fn schema_column_names(schema: &ModelSchema<'_>) -> PyResult( ) -> PyResult> { let py: Python<'py> = schema.declaration.py(); let enums: Bound<'py, PyDict> = PyDict::new(py); - for column in schema.declaration.getattr(COLUMNS_FIELD)?.try_iter()? { - let column_type: Bound<'py, PyAny> = column?.getattr(TYPE_FIELD)?; + for column in native_field(&schema.declaration, COLUMNS_FIELD)?.try_iter()? { + let column_type: Bound<'py, PyAny> = native_field(&column?, TYPE_FIELD)?; if column_type.is_none() { continue; } @@ -119,7 +120,7 @@ pub(crate) fn merged_columns<'py>( specs: Vec::new(), facts: Vec::new(), }; - for column in schema.declaration.getattr(COLUMNS_FIELD)?.try_iter()? { + for column in native_field(&schema.declaration, COLUMNS_FIELD)?.try_iter()? { let column: Bound<'py, PyAny> = column?; merged .facts @@ -167,13 +168,11 @@ fn augmented<'py>( (local, local_fact): (&Bound<'py, PyAny>, SchemaColumnFact), ) -> PyResult<(Bound<'py, PyAny>, SchemaColumnFact)> { let py: Python<'py> = named.py(); - let mut audits: Vec> = named - .getattr(AUDITS_FIELD)? + let mut audits: Vec> = native_field(named, AUDITS_FIELD)? .try_iter()? .collect::>()?; let mut fact: SchemaColumnFact = named_fact.clone(); - for (audit, definition) in local - .getattr(AUDITS_FIELD)? + for (audit, definition) in native_field(local, AUDITS_FIELD)? .try_iter()? .zip(local_fact.audit_definitions) { @@ -192,7 +191,7 @@ fn augmented<'py>( } let fields: Bound<'py, PyDict> = PyDict::new(py); for field in KEPT_NAMED_FIELDS { - fields.set_item(field, named.getattr(field)?)?; + fields.set_item(field, native_field(named, field)?)?; } fields.set_item(AUDITS_FIELD, PyTuple::new(py, audits)?)?; fields.set_item(MIGRATE_FROM_FIELD, &local_fact.migrate_from)?; @@ -245,14 +244,14 @@ fn check_augmentation( /// Where a named-schema column is declared, as its `path:line` or the named schema. fn origin(named: &Bound<'_, PyAny>) -> PyResult { - let location: Bound<'_, PyAny> = named.getattr(LOCATION_FIELD)?; + let location: Bound<'_, PyAny> = native_field(named, LOCATION_FIELD)?; if location.is_none() { return Ok("the named schema".to_owned()); } Ok(format!( "{}:{}", - location.getattr(PATH_FIELD)?.str()?, - location.getattr(LINE_FIELD)?.str()? + native_field(&location, PATH_FIELD)?.str()?, + native_field(&location, LINE_FIELD)?.str()? )) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_visibility.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_visibility.rs index fb31778a67..3042c27beb 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_visibility.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_visibility.rs @@ -125,21 +125,19 @@ impl<'py> ModelVisibility<'py> { let (enums, constants) = if classes.projects { let resolved: Bound<'py, PyAny> = self.resolved(context, model.file_path)?; for field in SHARED_FIELDS { - fields.set_item(field, resolved.getattr(field)?)?; + fields.set_item(field, native_field(&resolved, field)?)?; } fields.set_item( ENUM_VISIBILITY_FIELD, - resolved.getattr(ENUM_VISIBILITY_FIELD)?, + native_field(&resolved, ENUM_VISIBILITY_FIELD)?, )?; fields.set_item( CONSTANT_VISIBILITY_FIELD, - resolved.getattr(CONSTANT_VISIBILITY_FIELD)?, + native_field(&resolved, CONSTANT_VISIBILITY_FIELD)?, )?; ( - resolved.getattr(ENUMS_FIELD)?.downcast_into::()?, - resolved - .getattr(CONSTANTS_FIELD)? - .downcast_into::()?, + native_field(&resolved, ENUMS_FIELD)?.downcast_into::()?, + native_field(&resolved, CONSTANTS_FIELD)?.downcast_into::()?, ) } else { for field in [ @@ -204,9 +202,9 @@ fn rebound<'py>( ) -> PyResult> { let py: Python<'py> = cached.py(); let enum_visibility: Bound<'py, PyDict> = - cached.getattr(ENUM_VISIBILITY_FIELD)?.downcast_into()?; + native_field(cached, ENUM_VISIBILITY_FIELD)?.downcast_into()?; let constant_visibility: Bound<'py, PyDict> = - cached.getattr(CONSTANT_VISIBILITY_FIELD)?.downcast_into()?; + native_field(cached, CONSTANT_VISIBILITY_FIELD)?.downcast_into()?; if enum_visibility.is_empty() && constant_visibility.is_empty() { return Ok(cached.clone()); } @@ -215,7 +213,7 @@ fn rebound<'py>( native_field(resolver, "native_contexts")?.downcast_into()?; let fields: Bound<'py, PyDict> = PyDict::new(py); for field in VISIBLE_FIELDS { - fields.set_item(field, cached.getattr(field)?)?; + fields.set_item(field, native_field(cached, field)?)?; } fields.set_item( ENUM_VISIBILITY_FIELD, @@ -257,7 +255,7 @@ fn by_name<'py>(declarations: &Bound<'py, PyAny>) -> PyResult let named: Bound<'py, PyDict> = PyDict::new(declarations.py()); for declaration in declarations.try_iter()? { let declaration: Bound<'py, PyAny> = declaration?; - named.set_item(declaration.getattr(NAME_FIELD)?, declaration)?; + named.set_item(native_field(&declaration, NAME_FIELD)?, declaration)?; } Ok(named) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/project.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/project.rs index a663b6fbd2..c0422ad9ca 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/project.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/project.rs @@ -162,10 +162,7 @@ impl NativeCompiledProject { }; owners.push(model_owner( record.name.clone(), - record - .model_file - .bind(py) - .getattr(RELATIVE_PATH_ATTRIBUTE)? + native_field(record.model_file.bind(py), RELATIVE_PATH_ATTRIBUTE)? .str()? .to_string(), ( @@ -176,12 +173,10 @@ impl NativeCompiledProject { } for source in &self.sources { let attached: &AttachedSource = source.attached(); - let path: String = attached - .source_file - .bind(py) - .getattr(RELATIVE_PATH_ATTRIBUTE)? - .str()? - .to_string(); + let path: String = + native_field(attached.source_file.bind(py), RELATIVE_PATH_ATTRIBUTE)? + .str()? + .to_string(); owners.push(resource_owner( SOURCE_OWNER, attached.name.clone(), @@ -199,14 +194,10 @@ impl NativeCompiledProject { SEED_OWNER, name.to_owned(), ( - seed_file - .bind(py) - .getattr(RELATIVE_PATH_ATTRIBUTE)? + native_field(seed_file.bind(py), RELATIVE_PATH_ATTRIBUTE)? .str()? .to_string(), - schema_file - .bind(py) - .getattr(RELATIVE_PATH_ATTRIBUTE)? + native_field(schema_file.bind(py), RELATIVE_PATH_ATTRIBUTE)? .str()? .to_string(), ), @@ -452,10 +443,7 @@ impl NativeCompiledProject { .iter() .map(|record| { Ok(( - record - .model_file - .bind(py) - .getattr(RELATIVE_PATH_ATTRIBUTE)? + native_field(record.model_file.bind(py), RELATIVE_PATH_ATTRIBUTE)? .str()? .to_string(), record.references.clone(), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/project_view.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/project_view.rs index 59376c3924..53b1b9adb0 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/project_view.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/project_view.rs @@ -20,6 +20,7 @@ use crate::bindings::_helpers::compiled_project::source_views::NativeCompiledSou use crate::bindings::_helpers::graph::project_graph::{ NativeProjectGraph, ResourceRow, project_graph, }; +use crate::bindings::_helpers::records::fields::field as native_field; const MODELS_FIELD: &str = "models"; const RUN_ID_FIELD: &str = "run_id"; @@ -504,7 +505,7 @@ impl NativeProjectView { let expansion: Py = authored_expansion(&classes, record)?; if !expansion.is_none(py) { let path: Bound<'_, PyAny> = - record.model_file.bind(py).getattr(FILE_PATH_ATTRIBUTE)?; + native_field(record.model_file.bind(py), FILE_PATH_ATTRIBUTE)?; expansions.set_item(path, expansion)?; } } @@ -536,9 +537,10 @@ impl NativeProjectView { rows.push(match seed.cast::() { Ok(native) => native.get().graph_row(py)?, Err(_) => { - let tags: Bound<'_, PyAny> = seed - .getattr(SCHEMA_ENTRY_ATTRIBUTE)? - .getattr(TAGS_ATTRIBUTE)?; + let tags: Bound<'_, PyAny> = native_field( + &native_field(&seed, SCHEMA_ENTRY_ATTRIBUTE)?, + TAGS_ATTRIBUTE, + )?; resource_row(&seed, Some(&tags))? } }); @@ -548,7 +550,7 @@ impl NativeProjectView { rows.push(match function.cast::() { Ok(native) => native.get().graph_row(py)?, Err(_) => { - let tags: Bound<'_, PyAny> = function.getattr(TAGS_ATTRIBUTE)?; + let tags: Bound<'_, PyAny> = native_field(&function, TAGS_ATTRIBUTE)?; resource_row(&function, Some(&tags))? } }); @@ -567,7 +569,7 @@ fn resource_row( tags: Option<&Bound<'_, PyAny>>, ) -> PyResult { let mut deps: Vec<(String, String)> = Vec::new(); - for key in resource.getattr(DEPS_ATTRIBUTE)?.try_iter()? { + for key in native_field(resource, DEPS_ATTRIBUTE)?.try_iter()? { deps.push(key_row(&key?)?); } let mut tag_names: Vec = Vec::new(); @@ -577,7 +579,7 @@ fn resource_row( } } Ok(( - key_row(&resource.getattr(KEY_ATTRIBUTE)?)?, + key_row(&native_field(resource, KEY_ATTRIBUTE)?)?, deps, tag_names, None, @@ -587,7 +589,9 @@ fn resource_row( /// `(resource type, name)` of a `CompiledObjectKey`. pub(crate) fn key_row(key: &Bound<'_, PyAny>) -> PyResult<(String, String)> { Ok(( - key.getattr(RESOURCE_TYPE_ATTRIBUTE)?.str()?.extract()?, - key.getattr(NAME_ATTRIBUTE)?.extract()?, + native_field(key, RESOURCE_TYPE_ATTRIBUTE)? + .str()? + .extract()?, + native_field(key, NAME_ATTRIBUTE)?.extract()?, )) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/schema_facts.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/schema_facts.rs index 1124532b51..d72ac55f30 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/schema_facts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/schema_facts.rs @@ -1,5 +1,6 @@ //! Native facts of declared schema columns and families, read once from their Python specs. +use crate::bindings::_helpers::records::fields::field as native_field; use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyResult}; use sqlbuild_analysis::compiled_project::models::{ DynamicFamilyFact, ModelSchemaFact, SchemaColumnFact, @@ -33,12 +34,12 @@ pub(crate) fn python_column_fact( let mut audit_definitions: Vec = Vec::new(); if let Some(audits) = present(column, AUDITS_FIELD)? { for audit in audits.try_iter()? { - audit_definitions.push(audit?.getattr(DEFINITION_NAME_FIELD)?.extract()?); + audit_definitions.push(native_field(&audit?, DEFINITION_NAME_FIELD)?.extract()?); } } let location_path: Option = match present(column, LOCATION_FIELD)? { Some(location) => { - let path: Bound<'_, PyAny> = location.getattr(PATH_FIELD)?; + let path: Bound<'_, PyAny> = native_field(&location, PATH_FIELD)?; let outside: bool = match model_path { Some(model_path) => !path.is(model_path) && path.ne(model_path)?, None => true, @@ -50,7 +51,7 @@ pub(crate) fn python_column_fact( None => None, }; Ok(SchemaColumnFact { - name: column.getattr(NAME_FIELD)?.str()?.extract()?, + name: native_field(column, NAME_FIELD)?.str()?.extract()?, data_type: optional(column, TYPE_FIELD)?, nullable: optional(column, NULLABLE_FIELD)?, described: present(column, DESCRIPTION_FIELD)?.is_some(), @@ -80,23 +81,23 @@ pub(crate) fn entry_schema_fact( return Ok(None); } let mut dynamic_columns: Vec = Vec::new(); - for family in entry.getattr(DYNAMIC_COLUMNS_FIELD)?.try_iter()? { + for family in native_field(entry, DYNAMIC_COLUMNS_FIELD)?.try_iter()? { let family: Bound<'_, PyAny> = family?; dynamic_columns.push(family_fact(&DynamicFamily { - name: family.getattr(NAME_FIELD)?.extract()?, - pivot_column: family.getattr(PIVOT_COLUMN_FIELD)?.extract()?, - value_column: family.getattr(VALUE_COLUMN_FIELD)?.extract()?, - aggregate: family.getattr(AGGREGATE_FIELD)?.extract()?, - data_type: family.getattr(TYPE_FIELD)?.extract()?, - name_pattern: family.getattr(NAME_PATTERN_FIELD)?.extract()?, + name: native_field(&family, NAME_FIELD)?.extract()?, + pivot_column: native_field(&family, PIVOT_COLUMN_FIELD)?.extract()?, + value_column: native_field(&family, VALUE_COLUMN_FIELD)?.extract()?, + aggregate: native_field(&family, AGGREGATE_FIELD)?.extract()?, + data_type: native_field(&family, TYPE_FIELD)?.extract()?, + name_pattern: native_field(&family, NAME_PATTERN_FIELD)?.extract()?, })); } Ok(Some(ModelSchemaFact { - audit_count: u32::try_from(entry.getattr(AUDITS_FIELD)?.len()?).unwrap_or(u32::MAX), - columns: python_column_facts(&entry.getattr(COLUMNS_FIELD)?, Some(model_path))?, + audit_count: u32::try_from(native_field(entry, AUDITS_FIELD)?.len()?).unwrap_or(u32::MAX), + columns: python_column_facts(&native_field(entry, COLUMNS_FIELD)?, Some(model_path))?, dynamic_columns, - type_enforcement: entry.getattr(TYPE_ENFORCEMENT_FIELD)?.is_truthy()?, - named_schema: !entry.getattr(MODEL_SCHEMA_FIELD)?.is_none(), + type_enforcement: native_field(entry, TYPE_ENFORCEMENT_FIELD)?.is_truthy()?, + named_schema: !native_field(entry, MODEL_SCHEMA_FIELD)?.is_none(), })) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/seed_views.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/seed_views.rs index e270cd4b0e..197bacdc21 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/seed_views.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/seed_views.rs @@ -15,6 +15,7 @@ use crate::bindings::_helpers::compiled_project::model_input_view::{ }; use crate::bindings::_helpers::compiled_project::project::NativeCompiledProject; use crate::bindings::_helpers::graph::project_graph::ResourceRow; +use crate::bindings::_helpers::records::fields::field as native_field; const SEED_RESOURCE: &str = "seed"; const TAGS_ATTRIBUTE: &str = "tags"; @@ -54,27 +55,26 @@ pub(crate) fn paired_seeds( let py: Python<'_> = compile_error.py(); let mut names: Vec = Vec::with_capacity(declarations.len()); for (entry, _) in &declarations { - names.push(entry.bind(py).getattr(NAME_ATTRIBUTE)?.extract()?); + names.push(native_field(entry.bind(py), NAME_ATTRIBUTE)?.extract()?); } let mut stems: Vec = Vec::with_capacity(seed_files.len()); for seed_file in seed_files { stems.push( - seed_file - .bind(py) - .getattr(FILE_PATH_ATTRIBUTE)? - .getattr(STEM_ATTRIBUTE)? - .extract()?, + native_field( + &native_field(seed_file.bind(py), FILE_PATH_ATTRIBUTE)?, + STEM_ATTRIBUTE, + )? + .extract()?, ); } let pairs: Vec = match pair_seed_declarations(&names, &stems) { Ok(pairs) => pairs, Err(missing) => { let schema_file: &Py = &declarations[missing].1; - let relative_path: String = schema_file - .bind(py) - .getattr(RELATIVE_PATH_ATTRIBUTE)? - .str()? - .extract()?; + let relative_path: String = + native_field(schema_file.bind(py), RELATIVE_PATH_ATTRIBUTE)? + .str()? + .extract()?; let message: String = format!( "Seed declaration '{}' in {relative_path} has no matching CSV file under seeds/", names[missing] @@ -198,12 +198,7 @@ impl NativeCompiledSeed { pub(crate) fn graph_row(&self, py: Python<'_>) -> PyResult { read_seed(&self.owner, self.index, py, |seed, py| { let mut tags: Vec = Vec::new(); - for tag in seed - .schema_entry - .bind(py) - .getattr(TAGS_ATTRIBUTE)? - .try_iter()? - { + for tag in native_field(seed.schema_entry.bind(py), TAGS_ATTRIBUTE)?.try_iter()? { tags.push(tag?.str()?.extract()?); } Ok(( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/source_loop.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/source_loop.rs index 61d293301b..1807699f43 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/source_loop.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/source_loop.rs @@ -251,9 +251,8 @@ pub(crate) fn expanded_authored_sql<'py>( let macro_declarations: Bound<'py, PyAny> = if resolver.is_none() { py.None().into_bound(py) } else { - let contexts: Bound<'py, NativeDeclarationContexts> = resolver - .getattr(NATIVE_CONTEXTS_ATTRIBUTE)? - .downcast_into()?; + let contexts: Bound<'py, NativeDeclarationContexts> = + native_field(resolver, NATIVE_CONTEXTS_ATTRIBUTE)?.downcast_into()?; contexts .get() .resolved_context(resolver, file_path, None)? @@ -287,7 +286,7 @@ fn template_text( field: &str, label: &str, ) -> PyResult> { - let raw: Bound<'_, PyAny> = owner.getattr(field)?; + let raw: Bound<'_, PyAny> = native_field(owner, field)?; if raw.is_none() { return Ok(None); } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/sql_test_loop.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/sql_test_loop.rs index c37367dc7e..c330a2897d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/sql_test_loop.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/sql_test_loop.rs @@ -534,9 +534,9 @@ impl<'py> TestScope<'_, 'py> { ))); } let enum_visibility: Bound<'py, PyDict> = - declarations.getattr(ENUM_VISIBILITY)?.downcast_into()?; + native_field(declarations, ENUM_VISIBILITY)?.downcast_into()?; let constant_visibility: Bound<'py, PyDict> = - declarations.getattr(CONSTANT_VISIBILITY)?.downcast_into()?; + native_field(declarations, CONSTANT_VISIBILITY)?.downcast_into()?; for reference in scans.iter().flat_map(|scan| scan.references.iter()) { let visibility: &Bound<'py, PyDict> = match reference.kind { sqlbuild_sqltext::compiler::models::DeclarationReferenceKind::Enum => { @@ -561,9 +561,9 @@ impl<'py> TestScope<'_, 'py> { } } let macro_records: Bound<'py, PyDict> = - declarations.getattr(MACRO_RECORDS)?.downcast_into()?; + native_field(declarations, MACRO_RECORDS)?.downcast_into()?; let macro_visibility: Bound<'py, PyDict> = - declarations.getattr(MACRO_VISIBILITY)?.downcast_into()?; + native_field(declarations, MACRO_VISIBILITY)?.downcast_into()?; for name in top_level_macro_names(sql).map_err(|message| self.error(message))? { let Some(record) = macro_records.get_item(&name)? else { continue; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/values.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/values.rs index d9e7695d33..90a9f140d2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/values.rs @@ -8,6 +8,7 @@ use pyo3::types::{ use serde_json::{Map, Number, Value}; use crate::bindings::_helpers::boundary::panics::value_error; +use crate::bindings::_helpers::records::fields::field as native_field; const DATACLASS_FIELDS: &str = "__dataclass_fields__"; const INSTANCE_DICT: &str = "__dict__"; @@ -55,14 +56,14 @@ pub(crate) fn plain_value(value: &Bound<'_, PyAny>) -> PyResult { fn object_like_value(value: &Bound<'_, PyAny>) -> PyResult { let py = value.py(); if value.is_instance(&py.import(ENUM_MODULE)?.getattr(ENUM_TYPE)?)? { - return plain_value(&value.getattr(ENUM_VALUE)?); + return plain_value(&native_field(value, ENUM_VALUE)?); } if value.hasattr(DATACLASS_FIELDS)? && !value.is_instance_of::() { return dataclass_value(value); } let datetime = py.import(DATETIME_MODULE)?; - if value.is_instance(&datetime.getattr(DATE_TYPE)?)? - || value.is_instance(&datetime.getattr(TIME_TYPE)?)? + if value.is_instance(&native_field(&datetime, DATE_TYPE)?)? + || value.is_instance(&native_field(&datetime, TIME_TYPE)?)? { return Ok(Value::String( value.call_method0(ISOFORMAT)?.str()?.to_str()?.to_owned(), @@ -74,7 +75,7 @@ fn object_like_value(value: &Bound<'_, PyAny>) -> PyResult { /// A dataclass's public instance attributes, or its public fields when it has slots. fn dataclass_value(value: &Bound<'_, PyAny>) -> PyResult { let mut entries: Vec<(String, Value)> = Vec::new(); - if let Ok(attributes) = value.getattr(INSTANCE_DICT) { + if let Ok(attributes) = native_field(value, INSTANCE_DICT) { for (key, item) in attributes.downcast::()?.iter() { let name: String = key.str()?.to_str()?.to_owned(); if !name.starts_with(PRIVATE_PREFIX) { @@ -82,10 +83,10 @@ fn dataclass_value(value: &Bound<'_, PyAny>) -> PyResult { } } } else { - for key in value.getattr(DATACLASS_FIELDS)?.try_iter()? { + for key in native_field(value, DATACLASS_FIELDS)?.try_iter()? { let name: String = key?.str()?.to_str()?.to_owned(); if !name.starts_with(PRIVATE_PREFIX) { - let item = value.getattr(name.as_str())?; + let item = native_field(value, name.as_str())?; entries.push((name, plain_value(&item)?)); } } 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 45e55f2f77..b4de29494e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/contract_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/contract_command/command.rs @@ -153,7 +153,7 @@ fn exception(py: Python<'_>, error: &PyErr) -> bool { fn connection_timing(reporter: &Bound<'_, PyAny>, method: &str, started: Instant) -> PyResult<()> { let py: Python<'_> = reporter.py(); call_with( - &reporter.getattr(method)?, + &native_field(reporter, method)?, &[ ("connection_count", PyInt::new(py, 1).into_any()), ( @@ -319,7 +319,7 @@ fn finding_text(finding: Bound<'_, PyAny>) -> PyResult { fn resource_text(item: Bound<'_, PyAny>) -> PyResult { let mut parts: Vec = Vec::new(); for field in ["database", "schema", "relation"] { - let value: Bound<'_, PyAny> = item.getattr(field)?; + let value: Bound<'_, PyAny> = native_field(&item, field)?; if value.is_truthy()? { parts.push(value.str()?.to_string()); } 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 1d66d0b819..546dc0d42a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/contract_command/write.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/contract_command/write.rs @@ -11,6 +11,7 @@ use crate::bindings::_helpers::compile_driver::python_calls::keywords; use crate::bindings::_helpers::format_command::files::write_file_atomically; use crate::bindings::_helpers::records::construct::{enum_member, new_record}; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced_keywords; use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::refactor::contract_edits::edit_contract_model; use crate::bindings::_helpers::refactor::contract_edits::edit_contract_source; @@ -200,5 +201,5 @@ fn replaced<'py>( for (name, field) in fields { keywords.set_item(*name, field)?; } - imported(py, "dataclasses", "replace")?.call((value,), Some(&keywords)) + replaced_keywords(value, &keywords) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/contracts/effective_config.rs b/crates/sqlbuild-python/src/bindings/_helpers/contracts/effective_config.rs index b325a031dc..68efb2f2f2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/contracts/effective_config.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/contracts/effective_config.rs @@ -119,7 +119,7 @@ fn field_owner<'py>( field: &TargetField, ) -> PyResult> { match field.group { - Some(group) => target.getattr(group), + Some(group) => native_field(target, group), None => Ok(target.clone()), } } @@ -128,7 +128,7 @@ fn field_value<'py>( target: &Bound<'py, PyAny>, field: &TargetField, ) -> PyResult, Value<'py>>> { - let value: Bound<'py, PyAny> = field_owner(target, field)?.getattr(field.name)?; + let value: Bound<'py, PyAny> = native_field(&field_owner(target, field)?, field.name)?; match (field.rule, value.is_none()) { (MergeRule::Overlay { .. }, true) => Ok(FieldValue::Scalar(None)), (MergeRule::Overlay { .. }, false) => Ok(FieldValue::Scalar(Some(value))), @@ -321,7 +321,12 @@ pub(crate) fn effective_config_settings<'py>( let read = |settings: &Bound<'py, PyAny>| -> PyResult> { names .iter() - .map(|name| Ok((key(name.as_any())?, settings.getattr(name)?))) + .map(|name| { + Ok(( + key(name.as_any())?, + native_field(settings, &name.to_cow()?)?, + )) + }) .collect() }; let overridden: Vec> = overrides 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 be8ba96914..46f14fc301 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/contracts/spec_resolution.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/contracts/spec_resolution.rs @@ -176,8 +176,8 @@ pub(crate) fn resolve_effective_scenario_config<'py>( Ok(( *field, override_or( - local_limits.getattr(*field)?, - project_limits.getattr(*field)?, + native_field(&local_limits, field)?, + native_field(&project_limits, field)?, ), )) }) 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 653f9281bf..e73c488da8 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_command/command.rs @@ -25,6 +25,7 @@ use crate::bindings::_helpers::cost_report::functions::list_cost_runs; use crate::bindings::_helpers::cost_report::functions::resolve_cost_run; use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced_keywords; /// The request fields every cost view reads. struct CostRequest<'py> { @@ -242,14 +243,12 @@ fn with_resources<'py>( resources: &[Bound<'py, PyAny>], ) -> PyResult> { let py: Python<'py> = record.py(); - let replace: Bound<'py, PyAny> = imported(py, "dataclasses", "replace")?; let cost_changes: Bound<'py, PyDict> = PyDict::new(py); cost_changes.set_item("resources", PyTuple::new(py, resources)?)?; - let cost: Bound<'py, PyAny> = - replace.call((native_field(record, "cost")?,), Some(&cost_changes))?; + let cost: Bound<'py, PyAny> = replaced_keywords(&native_field(record, "cost")?, &cost_changes)?; let changes: Bound<'py, PyDict> = PyDict::new(py); changes.set_item("cost", cost)?; - replace.call((record,), Some(&changes)) + replaced_keywords(record, &changes) } fn run_detail(request: &CostRequest<'_>) -> PyResult { 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 5306dc5e70..fade4b27b9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_command/refresh.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_command/refresh.rs @@ -23,6 +23,7 @@ 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::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced_keywords; fn coverage(summary: &Bound<'_, PyAny>) -> PyResult { Ok(CostCoverage { @@ -173,8 +174,7 @@ fn refresh(py: Python<'_>, request: &Bound<'_, PyAny>) -> PyResult<()> { } let changes: Bound<'_, PyDict> = PyDict::new(py); changes.set_item("cost", summary)?; - let refreshed: Bound<'_, PyAny> = - imported(py, "dataclasses", "replace")?.call((record,), Some(&changes))?; + let refreshed: Bound<'_, PyAny> = replaced_keywords(&record, &changes)?; write_cost_run(py, project_dir.extract::()?, &refreshed) .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; Ok(()) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cost_command/sorting.rs b/crates/sqlbuild-python/src/bindings/_helpers/cost_command/sorting.rs index 0727f3360e..91bb50c231 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_command/sorting.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_command/sorting.rs @@ -33,7 +33,7 @@ fn sorted_with<'py>( fn attribute(item: &Bound<'_, PyAny>, path: &str) -> PyResult> { let mut value: Bound<'_, PyAny> = item.clone(); for name in path.split('.') { - value = value.getattr(name)?; + value = native_field(&value, name)?; } Ok(value.unbind()) } 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 a78f31296b..754cfae34f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_report/degradation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_report/degradation.rs @@ -157,7 +157,7 @@ fn cost_record<'py>( "started_at", "completed_at", ] { - fields.push((name, finalization.getattr(name)?)); + fields.push((name, native_field(finalization, name)?)); } fields.push(("cost", summary)); fields.push(( 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 b759e9dfb3..407315978e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_report/records.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_report/records.rs @@ -24,24 +24,26 @@ pub(crate) fn artifact_error(py: Python<'_>, error: &CostArtifactError) -> PyErr } fn text(value: &Bound<'_, PyAny>, field: &str) -> PyResult { - value.getattr(field)?.extract() + native_field(value, field)?.extract() } fn optional_text(value: &Bound<'_, PyAny>, field: &str) -> PyResult> { - value.getattr(field)?.extract() + native_field(value, field)?.extract() } /// `str(value.)`, the text a `Decimal` field is stored as. fn decimal(value: &Bound<'_, PyAny>, field: &str) -> PyResult { - Ok(value.getattr(field)?.str()?.to_string()) + Ok(native_field(value, field)?.str()?.to_string()) } fn integer(value: &Bound<'_, PyAny>, field: &str) -> PyResult { - value.getattr(field)?.extract() + native_field(value, field)?.extract() } fn timestamp(value: &Bound<'_, PyAny>, field: &str) -> PyResult { - value.getattr(field)?.call_method0("isoformat")?.extract() + native_field(value, field)? + .call_method0("isoformat")? + .extract() } fn resource(value: &Bound<'_, PyAny>) -> PyResult { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/allocation.rs b/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/allocation.rs index a500b71a7f..a0162e96cd 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/allocation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/allocation.rs @@ -23,7 +23,8 @@ const PENDING_MESSAGE: &str = "Snowflake query history is not complete yet."; /// One `QueryCostObservation` as a native query, in the run when its run ID matches. pub(crate) fn cost_query(observation: &Bound<'_, PyAny>, run_id: &str) -> PyResult { - let text = |name: &str| -> PyResult> { observation.getattr(name)?.extract() }; + let text = + |name: &str| -> PyResult> { native_field(observation, name)?.extract() }; Ok(CostQuery { query_id: native_field(observation, "query_id")?.extract()?, warehouse: WarehouseKey { 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 786f5718cb..0d80fbf9af 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/context.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/context.rs @@ -69,7 +69,7 @@ fn or_current<'py>( if !value.is_none() || current.is_none() { return Ok(value); } - current.getattr(field) + native_field(current, field) } fn scope_of(context: Bound<'_, PyAny>) -> CostScope { 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 66e5165d36..405562e494 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/snowflake.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/snowflake.rs @@ -22,6 +22,7 @@ use crate::bindings::_helpers::cost_telemetry::ledger::read_ledger; use crate::bindings::_helpers::cursor::values::DATETIME_MODULE; use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced_keywords; use crate::bindings::_helpers::records::ids::RecordId; const DEADLINE_SECONDS: f64 = 10.0; @@ -444,7 +445,7 @@ fn ledger_attributed<'py>( pyo3::types::PyDictMethods::get_item(ledger, native_field(observation, "query_id")?)?; let field = |name: &str| -> PyResult> { match &entry { - Some(entry) => entry.getattr(name), + Some(entry) => native_field(entry, name), None => Ok(py.None().into_bound(py)), } }; @@ -452,7 +453,7 @@ fn ledger_attributed<'py>( changes.set_item("run_id", field("run_id")?)?; changes.set_item("resource_type", field("resource_type")?)?; changes.set_item("resource_name", field("resource_name")?)?; - imported(py, "dataclasses", "replace")?.call((observation,), Some(&changes)) + replaced_keywords(observation, &changes) } /// Collection inputs read from the keyword arguments. 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 b615bace40..ab41216edd 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/values.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cost_telemetry/values.rs @@ -9,6 +9,7 @@ use sqlbuild_core::cost_allocation::models::PyDecimal; use crate::bindings::_helpers::compile_driver::python_calls::imported; use crate::bindings::_helpers::cursor::values::DATETIME_MODULE; +use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::state_rows::timestamps::offset_aware; const DECIMAL_MODULE: &str = "decimal"; @@ -18,7 +19,7 @@ const COST_AWARE_METHODS: [&str; 2] = ["cost_capability", "collect_run_cost"]; /// Whether `adapter` implements the cost telemetry methods. pub(crate) fn cost_aware(adapter: &Bound<'_, PyAny>) -> PyResult { for name in COST_AWARE_METHODS { - if !adapter.hasattr(name)? || adapter.getattr(name)?.is_none() { + if !adapter.hasattr(name)? || native_field(adapter, name)?.is_none() { return Ok(false); } } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/cursor/durations.rs b/crates/sqlbuild-python/src/bindings/_helpers/cursor/durations.rs index ef510ee59f..748ca20656 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/cursor/durations.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/cursor/durations.rs @@ -43,7 +43,7 @@ pub(crate) fn parse_cursor_duration<'py>( } fn duration_span(duration: &Bound<'_, PyAny>) -> PyResult { - let field = |name: &str| -> PyResult { duration.getattr(name)?.extract() }; + let field = |name: &str| -> PyResult { native_field(duration, name)?.extract() }; Ok(DurationSpan { months: field("years")? * MONTHS_PER_YEAR + field("months")?, days: field("days")?, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/dag_artifact/rows.rs b/crates/sqlbuild-python/src/bindings/_helpers/dag_artifact/rows.rs index 3ef6e485fe..ba0b4e5305 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dag_artifact/rows.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dag_artifact/rows.rs @@ -23,7 +23,7 @@ const CHECK_KIND: &str = "check"; const DEFAULT_MATERIALIZATION: &str = "view"; fn attribute<'py>(value: &Bound<'py, PyAny>, name: &str) -> PyResult> { - value.getattr(name) + native_field(value, name) } fn text(value: &Bound<'_, PyAny>, name: &str) -> PyResult { 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 7c5293cce2..d609740a4d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/results.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dagster_invocation/results.rs @@ -470,7 +470,7 @@ fn envelope_fields<'py>( ) -> PyResult)>> { names .iter() - .map(|name| Ok(((*name).to_owned(), envelope.getattr(*name)?))) + .map(|name| Ok(((*name).to_owned(), native_field(envelope, name)?))) .collect() } 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 c295f1379c..d79bb6cbb3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/init.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_command/init.rs @@ -63,7 +63,7 @@ fn dbt_project_dir<'py>(request: &Bound<'py, PyAny>) -> PyResult, name: &str) -> PyResult { - Ok(result.getattr(name)?.str()?.to_string()) + Ok(native_field(result, name)?.str()?.to_string()) } fn summary(result: &Bound<'_, PyAny>) -> PyResult { 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 56fc6e332c..b1d3fd6abc 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 @@ -323,7 +323,7 @@ fn dbt_debug_warning(paths: &InitPaths<'_>, target_name: &str) -> PyResult, name: &str, message: &str) -> PyResult<()> { - let callback: Bound<'_, PyAny> = callbacks.getattr(name)?; + let callback: Bound<'_, PyAny> = native_field(callbacks, name)?; if !callback.is_none() { callback.call1((message,))?; } 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 b3673d584d..f759e483c5 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_events/results.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_events/results.rs @@ -6,6 +6,7 @@ use pyo3::types::{PyString, PyTuple}; use sqlbuild_core::dbt_events::models::{DbtNodeMessage, DbtNodeResult}; use crate::bindings::_helpers::records::construct::new_record_with; +use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::records::ids::RecordId; fn optional<'py, T: pyo3::IntoPyObject<'py>>( @@ -76,7 +77,7 @@ pub(crate) fn python_result<'py>( } fn field pyo3::FromPyObject<'a>>(value: &Bound<'_, PyAny>, name: &str) -> PyResult { - value.getattr(name)?.extract() + native_field(value, name)?.extract() } /// A Python `DbtNodeExecutionResult` as plain data. 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 b2631d872b..eb2dcac4de 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/cli.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/dbt_interop/cli.rs @@ -48,7 +48,7 @@ fn optional_text(value: &Bound<'_, PyAny>) -> PyResult> { } fn attribute_text(object: &Bound<'_, PyAny>, name: &str) -> PyResult> { - optional_text(&object.getattr(name)?) + optional_text(&native_field(object, name)?) } fn texts(values: &Bound<'_, PyAny>) -> PyResult> { @@ -265,7 +265,7 @@ pub(crate) fn raised(py: Python<'_>, class_name: ErrorId, error: DbtCliError) -> fn parsed_flags(parsed: &Bound<'_, PyAny>) -> PyResult { let value = |name: &str| attribute_text(parsed, name); - let flag = |name: &str| -> PyResult { parsed.getattr(name)?.is_truthy() }; + let flag = |name: &str| -> PyResult { native_field(parsed, name)?.is_truthy() }; Ok(DbtParsedFlags { select: texts(&native_field(parsed, "select")?)?, exclude: texts(&native_field(parsed, "exclude")?)?, 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 36a92e0dc3..4450fc5cda 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 @@ -6,9 +6,7 @@ use pyo3::{pyfunction, wrap_pyfunction}; use crate::bindings::_helpers::build_command::support::{boolean, emit, none, text, tuple}; use crate::bindings::_helpers::cli::progress_reporters::ConnectionProgressReporter; -use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, imported, keywords, -}; +use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, keywords}; use crate::bindings::_helpers::contracts::spec_resolution::resolve_target_config; use crate::bindings::_helpers::dbt_interop::execution::render_dbt_execution_summary_footer; use crate::bindings::_helpers::dbt_interop::pipeline_prologue::progress; @@ -19,6 +17,7 @@ use crate::bindings::_helpers::dbt_interop::runtime_execution::execute_dbt_comma use crate::bindings::_helpers::dbt_interop::sqlbuild_work::execute_dbt_sqlbuild_work; use crate::bindings::_helpers::records::construct::{new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced; use crate::bindings::_helpers::records::ids::RecordId; const OPTIONS: (&[&str], &[&str]) = ( @@ -83,15 +82,12 @@ pub(crate) fn write_dbt_execution_plan_text( let display = if merged.is_none() { plan } else { - imported(py, "dataclasses", "replace")?.call( - (plan,), - Some(&keywords( - py, - &[ - ("dbt_command_argv", merged), - ("supplemental_dbt_command_argvs", tuple(py, &[])?), - ], - )?), + replaced( + &plan, + &[ + ("dbt_command_argv", merged), + ("supplemental_dbt_command_argvs", tuple(py, &[])?), + ], )? }; let limit = if native_field(&request, "verbose")?.is_truthy()? { 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 669180f0db..ce896e3a80 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 @@ -13,7 +13,7 @@ use crate::bindings::_helpers::compile_attachment::effective_config::build_effec use crate::bindings::_helpers::compile_driver::deferral::resolve_model_deferral_inputs; use crate::bindings::_helpers::compile_driver::plan_result::connection_with_hooks; use crate::bindings::_helpers::compile_driver::python_calls::{ - KeywordArguments, call_with, imported, keywords, + KeywordArguments, call_with, keywords, }; use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; @@ -21,6 +21,7 @@ use crate::bindings::_helpers::planner_entries::display_plan::build_display_only use crate::bindings::_helpers::planner_entries::execution_plan::build_execution_plan; use crate::bindings::_helpers::records::construct::{new_record, new_record_with}; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -100,7 +101,7 @@ pub(crate) fn connection_hooks<'py>( pairs.push(( name, keyword_relay( - reporter.getattr(name)?, + native_field(reporter, name)?, &["connection_count", "elapsed_seconds"], )?, )); @@ -121,7 +122,7 @@ fn open_connection<'py>( native_field(hooks, "on_connection_start")?, )]; for name in ["on_connection_complete", "on_connection_error"] { - let callback = hooks.getattr(name)?; + let callback = native_field(hooks, name)?; pairs.push(( name, if callback.is_none() { @@ -292,10 +293,7 @@ pub(crate) fn attach_sqlbuild_plan_output<'py>( if output.is_none() { return Ok(plan); } - imported(py, "dataclasses", "replace")?.call( - (plan,), - Some(&keywords(py, &[("sqlbuild_plan_output", output)])?), - ) + replaced(&plan, &[("sqlbuild_plan_output", output)]) } /// Prefer stderr for dbt subprocess failure details, retaining Python whitespace semantics. 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 398092fc20..60b5e714f9 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 @@ -50,7 +50,7 @@ fn compile_input_error(py: Python<'_>, message: String, code: &str) -> PyErr { } fn text(value: &Bound<'_, PyAny>, name: &str) -> PyResult { - value.getattr(name)?.extract() + native_field(value, name)?.extract() } /// External reference resolver backed by a dbt manifest index. 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 d48a5d991d..1a2e165ada 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 @@ -65,7 +65,7 @@ fn write_outputs(py: Python<'_>, outcome: &DbtExecutionOutcome) -> PyResult<()> Stream::Stdout => "stdout", Stream::Stderr => "stderr", }; - sys.getattr(name)?.call_method1("write", (text,))?; + native_field(&sys, name)?.call_method1("write", (text,))?; } Ok(()) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/diagnostics/console_records.rs b/crates/sqlbuild-python/src/bindings/_helpers/diagnostics/console_records.rs index 9b958d2487..05bd5f97e3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/diagnostics/console_records.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/diagnostics/console_records.rs @@ -30,7 +30,7 @@ fn text_field(record: &Bound<'_, PyAny>, field: &str) -> PyResult if !record.hasattr(field)? { return Ok(None); } - let value: Bound<'_, PyAny> = record.getattr(field)?; + let value: Bound<'_, PyAny> = native_field(record, field)?; if value.is_instance_of::() { return Ok(Some(value.extract()?)); } @@ -42,7 +42,7 @@ fn duration_seconds(record: &Bound<'_, PyAny>) -> PyResult> { if !record.hasattr(DURATION_FIELD)? { return Ok(None); } - let value: Bound<'_, PyAny> = record.getattr(DURATION_FIELD)?; + let value: Bound<'_, PyAny> = native_field(record, DURATION_FIELD)?; if !value.is_instance_of::() { return Ok(None); } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/diff_report/facts.rs b/crates/sqlbuild-python/src/bindings/_helpers/diff_report/facts.rs index aa39b587ca..c5ad6e3502 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/diff_report/facts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/diff_report/facts.rs @@ -172,7 +172,7 @@ fn row_facts(rows: Bound<'_, PyAny>) -> PyResult> { if rows.is_none() { return Ok(None); } - let count = |name: &str| -> PyResult { rows.getattr(name)?.extract() }; + let count = |name: &str| -> PyResult { native_field(&rows, name)?.extract() }; let mut columns: Vec = Vec::new(); for item in native_field(&rows, "column_results")?.try_iter()? { columns.push(column(&item?)?); @@ -227,7 +227,7 @@ fn model_facts(model: &Bound<'_, PyAny>) -> PyResult { let schema: Bound<'_, PyAny> = native_field(model, "schema_result")?; let names = |attribute: &str| -> PyResult> { let mut names: Vec = Vec::new(); - for column in schema.getattr(attribute)?.try_iter()? { + for column in native_field(&schema, attribute)?.try_iter()? { names.push(text_of(&native_field(&column?, "name")?)?); } Ok(names) 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 07b40f56bb..336d7d50aa 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/diff_report/query.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/diff_report/query.rs @@ -244,7 +244,7 @@ pub(crate) fn prepare_query_diff<'py>( .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))? .downcast_into()?; let label = |name: &str, default: &str| -> PyResult { - let value: Bound<'py, PyAny> = request.getattr(name)?; + let value: Bound<'py, PyAny> = native_field(request, name)?; let text: String = if value.is_truthy()? { value.extract()? } else { @@ -256,7 +256,7 @@ pub(crate) fn prepare_query_diff<'py>( let examples = |name: &str| -> PyResult> { object( py, - effective_max_examples(request.getattr(name)?.extract()?, verbose), + effective_max_examples(native_field(request, name)?.extract()?, verbose), ) }; call( @@ -307,7 +307,7 @@ pub(crate) fn execution_options<'py>( extra: &[(&str, &Bound<'py, PyAny>)], ) -> PyResult> { let py: Python<'py> = request.py(); - let attribute = |name: &str| request.getattr(name); + let attribute = |name: &str| native_field(request, name); let sampling: Bound<'py, PyAny> = call( &host::entry(py, ExecutorHost::RowDiffSamplingOverride)?, &[ @@ -492,7 +492,7 @@ fn compare_artifacts<'py>( ("adapter", &adapter), ("connection", connection), ("artifact", artifact), - ("sql", &preparation.getattr(sql)?), + ("sql", &native_field(preparation, sql)?), ("expires_at", &expires_at), ], ) { @@ -716,11 +716,11 @@ fn inspect_inputs<'py>( &native_field(&adapter, "query_column_names")?, &[ ("connection", connection), - ("sql", &preparation.getattr(sql)?), + ("sql", &native_field(preparation, sql)?), ], )? .extract()?; - let label: String = preparation.getattr(label)?.extract()?; + let label: String = native_field(preparation, label)?.extract()?; query_column_problem(&label, &columns, max_columns) .map_err(|error| user_failure(py, error))?; } 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 103090db23..636bde0e65 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/diff_report/support.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/diff_report/support.rs @@ -117,7 +117,7 @@ fn texts(value: Bound<'_, PyAny>) -> PyResult> { /// The facts of one `DiffCommandRequest` that validation and the size guard read. pub(crate) fn request_facts(request: &Bound<'_, PyAny>) -> PyResult { let py: Python<'_> = request.py(); - let attribute = |name: &str| request.getattr(name); + let attribute = |name: &str| native_field(request, name); let present = |name: &str| -> PyResult { Ok(!attribute(name)?.is_none()) }; let number = |name: &str| -> PyResult> { attribute(name)?.extract() }; let flag = |name: &str| -> PyResult { attribute(name)?.is_truthy() }; 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 e84bc01802..2700d8b6dc 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/diff_report/tolerances.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/diff_report/tolerances.rs @@ -281,7 +281,7 @@ fn resolve_row_diff_sampling<'py>( } let label: String = request.get("label")?.extract()?; let overridden = |field: &str, raw: &str| -> PyResult> { - let value: Bound<'py, PyAny> = override_values.getattr(field)?; + let value: Bound<'py, PyAny> = native_field(&override_values, field)?; if value.is_none() { request.get(raw) } else { @@ -330,8 +330,8 @@ fn merge_row_diff_tolerances<'py>( } let merged = |field: &str| -> PyResult> { let combined: Bound<'py, PyDict> = PyDict::new(py); - combined.update(base.getattr(field)?.downcast()?.as_mapping())?; - combined.update(overlay.getattr(field)?.downcast()?.as_mapping())?; + combined.update(native_field(&base, field)?.downcast()?.as_mapping())?; + combined.update(native_field(&overlay, field)?.downcast()?.as_mapping())?; Ok(combined.into_any()) }; call_with( 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 bf4698cb96..785b0f78a6 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/hook_entries.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/hook_entries.rs @@ -25,7 +25,7 @@ fn sql_hook<'py>(entry: &Bound<'py, PyAny>, sql_fields: &[String]) -> PyResult = entry.getattr(*name)?; + let mut value: Bound<'py, PyAny> = native_field(entry, name)?; if value.is_none() { continue; } 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 4653475d06..a9e33da9d4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/imported_providers.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/imported_providers.rs @@ -336,7 +336,7 @@ pub(crate) fn bind_event_exporter_declarations<'py>( "event_kinds", "min_severity", ] { - pairs.push((field, declaration.getattr(field)?)); + pairs.push((field, native_field(&declaration, field)?)); } pairs.push(("provider_usages", usages)); bound.push(discovered(py, RecordId::DiscoveredEventExporter, &pairs)?); 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 2fb552b3ee..4f1ee4a910 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 @@ -135,7 +135,7 @@ impl<'py> Buckets<'py> { ("function", function.clone()), ]; for field in kind.fields { - pairs.push((field, read.getattr(*field)?)); + pairs.push((field, native_field(&read, field)?)); } pairs.push(("provider_usages", provider_usages(function, providers)?)); let get = |name: &str| -> PyResult> { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/model_records.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/model_records.rs index 8863216968..3b11b2612d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/model_records.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/model_records.rs @@ -14,6 +14,7 @@ use crate::bindings::_helpers::discovery::project_files::{model_record, path_obj 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::discovery::sorted_keys::sorted_keys; +use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::models::NativeProjectTree; const PATHLIB_MODULE: &str = "pathlib"; @@ -91,7 +92,7 @@ pub(crate) fn discovered_model_files<'py>( let mut records: Vec> = Vec::with_capacity(items.len()); for (relative_path, built) in items { if let Some(selected) = &selected - && !selected.contains(relative_path.getattr(STEM_ATTRIBUTE)?)? + && !selected.contains(native_field(&relative_path, STEM_ATTRIBUTE)?)? { continue; } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/project_facts.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/project_facts.rs index 0bc9b73ea8..c894f0bac0 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/project_facts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/project_facts.rs @@ -80,23 +80,23 @@ pub(crate) fn each( read: impl Fn(&Bound<'_, PyAny>) -> PyResult, ) -> PyResult> { let mut items: Vec = Vec::new(); - for item in owner.getattr(name)?.try_iter()? { + for item in native_field(owner, name)?.try_iter()? { items.push(read(&item?)?); } Ok(items) } fn text(owner: &Bound<'_, PyAny>, name: &str) -> PyResult { - Ok(owner.getattr(name)?.str()?.to_string()) + Ok(native_field(owner, name)?.str()?.to_string()) } fn optional_text(owner: &Bound<'_, PyAny>, name: &str) -> PyResult> { - owner.getattr(name)?.extract() + native_field(owner, name)?.extract() } /// `str(Path(...).stem)` of the path attribute `name`. pub(crate) fn stem(owner: &Bound<'_, PyAny>, name: &str) -> PyResult { - native_field(&owner.getattr(name)?, "stem")?.extract() + native_field(&native_field(owner, name)?, "stem")?.extract() } /// `(resource.name, str(resource.relative_path))`. @@ -124,7 +124,7 @@ fn relative_stem_named(file: &Bound<'_, PyAny>) -> PyResult { } fn enum_value(owner: &Bound<'_, PyAny>, name: &str) -> PyResult { - native_field(&owner.getattr(name)?, "value")?.extract() + native_field(&native_field(owner, name)?, "value")?.extract() } fn model_fact(model: &Bound<'_, PyAny>) -> PyResult { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/project_files.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/project_files.rs index 405ac70b26..2042228633 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/project_files.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/project_files.rs @@ -17,6 +17,7 @@ use crate::bindings::_helpers::discovery::header_declarations::{ }; use crate::bindings::_helpers::discovery::model_file_record::{ModelFileParts, NativeModelFile}; use crate::bindings::_helpers::discovery::raised::failure_error; +use crate::bindings::_helpers::records::fields::field as native_field; use crate::bindings::_helpers::sqltext::authored_values::map_to_python; use crate::bindings::models::{ModelTextRequest, NativeProjectTree}; use crate::bindings::types::CompilerDetach; @@ -60,7 +61,7 @@ pub(crate) fn model_record<'py>( let header_values: Bound<'py, PyDict> = map_to_python(py, model.header_values)? .into_bound(py) .downcast_into::()?; - let model_name: String = file_path.getattr(STEM_ATTRIBUTE)?.extract()?; + let model_name: String = native_field(&file_path, STEM_ATTRIBUTE)?.extract()?; let declarations: HeaderDeclarations<'py> = match model_header_declarations( py, ( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/project_validation.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/project_validation.rs index f2246ac55a..92d7a38846 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/project_validation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/project_validation.rs @@ -60,7 +60,7 @@ fn validate_discovered_project_inputs( } raised( py.import(BUILTINS_MODULE) - .and_then(|module| module.getattr(error.class.as_str())) + .and_then(|module| native_field(&module, error.class.as_str())) .and_then(|class| class.call1((error.message,))), ) } 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 fc8ee39c87..87e2c5175d 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/dispatcher.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/event_exporting/dispatcher.rs @@ -326,7 +326,7 @@ pub(crate) fn stop_event_exporter_threads(py: Python<'_>) -> PyResult<()> { .try_iter()? { let thread: Bound<'_, PyAny> = thread?; - let Ok(worker) = thread.getattr(WORKER_ATTRIBUTE) else { + let Ok(worker) = native_field(&thread, WORKER_ATTRIBUTE) else { continue; }; if let Ok(worker) = worker.downcast::() { 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 f56f2678ba..6e015d3b3c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/execution_history/conversion.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/execution_history/conversion.rs @@ -260,7 +260,7 @@ pub(crate) fn envelope_of(event: &Bound<'_, PyAny>) -> PyResult { if known_event(event)? { envelope.schema_version = native_field(event, "schema_version")?.extract()?; for (slot, name) in envelope.text_fields.iter_mut().zip(ENVELOPE_TEXT_FIELDS) { - *slot = optional_text(&event.getattr(name)?)?; + *slot = optional_text(&native_field(event, name)?)?; } envelope.occurred_at = Some(utc_time(&native_field(event, "occurred_at")?)?); return Ok(envelope); @@ -300,7 +300,7 @@ pub(crate) fn projection_fact(stored: &Bound<'_, PyAny>) -> PyResult) -> PyResult { let optional_time = |name: &str| -> PyResult> { - let value: Bound<'_, PyAny> = record.getattr(name)?; + let value: Bound<'_, PyAny> = native_field(record, name)?; if value.is_none() { return Ok(None); } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/fingerprints/storage_sql.rs b/crates/sqlbuild-python/src/bindings/_helpers/fingerprints/storage_sql.rs index 2ebc2de696..15da3c0292 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/fingerprints/storage_sql.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/fingerprints/storage_sql.rs @@ -66,7 +66,7 @@ fn stored_fingerprint(fingerprint: &Bound<'_, PyAny>) -> PyResult, name: &str) -> PyResult { - fingerprint.getattr(name)?.str()?.extract() + native_field(fingerprint, name)?.str()?.extract() } fn storage_error(py: Python<'_>, error: FingerprintStorageError) -> PyErr { 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 d3bd24ebe5..4f79813aa6 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/freshness/freshness_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/freshness/freshness_command.rs @@ -135,7 +135,7 @@ fn previous_records<'py>( let mut database: Option> = None; let mut schemas: BTreeSet = BTreeSet::new(); for collection in ["models", "seeds", "functions"] { - for item in project.getattr(collection)?.try_iter()? { + for item in native_field(project, collection)?.try_iter()? { let destination: Bound<'py, PyAny> = native_field(&item?, "destination")?; let destination_database: Bound<'py, PyAny> = native_field(&destination, "database")?; if database.is_none() && !destination_database.is_none() { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/freshness/observation.rs b/crates/sqlbuild-python/src/bindings/_helpers/freshness/observation.rs index 1a0c3751c1..22e7a621a1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/freshness/observation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/freshness/observation.rs @@ -6,7 +6,7 @@ use pyo3::{pyfunction, wrap_pyfunction}; use sqlbuild_state::freshness::main::single_value_problem::single_value_problem; use sqlbuild_state::freshness::models::QueryShape; -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::freshness::support::{ OBSERVATION_ERROR, exception_text, freshness_error, presumed_utc_datetime, presumed_utc_value, text, @@ -19,6 +19,7 @@ use crate::bindings::_helpers::records::construct::{ enum_from_value, enum_member, new_record, new_record_with, }; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced_keywords; use crate::bindings::_helpers::records::ids::RecordId; use crate::bindings::_helpers::references::source_relations::render_source_relation; use crate::bindings::_helpers::state_rows::freshness::{normalize, version_hash}; @@ -83,7 +84,7 @@ fn replaced_source<'py>( ) -> PyResult> { let changes: Bound<'py, PyDict> = PyDict::new(source.py()); changes.set_item("freshness", config)?; - imported(source.py(), "dataclasses", "replace")?.call((source,), Some(&changes)) + replaced_keywords(source, &changes) } fn adapter_strategy(py: Python<'_>) -> PyResult> { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/freshness/publication.rs b/crates/sqlbuild-python/src/bindings/_helpers/freshness/publication.rs index e96faf23ab..d1d5fe591c 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/freshness/publication.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/freshness/publication.rs @@ -14,6 +14,7 @@ use crate::bindings::_helpers::native_host::executor::ExecutorHost; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced_keywords; use crate::bindings::_helpers::records::ids::RecordId; /// Observed records grouped by `(database, schema)` in first-seen order. @@ -126,7 +127,6 @@ fn eligible_records<'py>( let record: Bound<'py, PyAny> = record?; observed.set_item(native_field(&record, "identity")?, record)?; } - let replace: Bound<'py, PyAny> = imported(py, "dataclasses", "replace")?; let changes: Bound<'py, PyDict> = PyDict::new(py); changes.set_item("run_id", run_id)?; let mut grouped: RecordsByLocation<'py> = Vec::new(); @@ -146,7 +146,7 @@ fn eligible_records<'py>( let Some(record) = observed.get_item(identity)? else { continue; }; - let replaced: Bound<'py, PyAny> = replace.call((record,), Some(&changes))?; + let replaced: Bound<'py, PyAny> = replaced_keywords(&record, &changes)?; grouped = collect_record(grouped, &replaced, (&affected, &entries_by_name))?; } Ok(grouped) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/frontier/encoder.rs b/crates/sqlbuild-python/src/bindings/_helpers/frontier/encoder.rs index ddb868ca9f..d7dd0cabb1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/frontier/encoder.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/frontier/encoder.rs @@ -90,7 +90,7 @@ pub(crate) fn qualified_name(value: &Bound<'_, PyAny>) -> PyResult { /// `getattr(value, name, None)`. fn attribute_or_none<'py>(value: &Bound<'py, PyAny>, name: &str) -> PyResult> { let py: Python<'py> = value.py(); - match value.getattr(name) { + match native_field(value, name) { Ok(attribute) => Ok(attribute), Err(error) if error.is_instance_of::(py) => { Ok(py.None().into_bound(py)) @@ -369,7 +369,7 @@ impl<'py> CaptureEncoder<'py> { Some(names) => Some( names .iter() - .map(|name| Ok(((*name).to_owned(), value.getattr(*name)?))) + .map(|name| Ok(((*name).to_owned(), native_field(value, name)?))) .collect::>()?, ), None => instance_attributes(value)?, @@ -442,7 +442,7 @@ fn instance_attributes<'py>(value: &Bound<'py, PyAny>) -> PyResult = value.getattr(text.as_str())?; + let item: Bound<'py, PyAny> = native_field(value, text.as_str())?; attributes = with_entry(attributes, text, item); } } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/graph/project_graph.rs b/crates/sqlbuild-python/src/bindings/_helpers/graph/project_graph.rs index 7ab69a9101..80be21806a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/graph/project_graph.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/graph/project_graph.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; -use pyo3::prelude::{Bound, Py, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; +use pyo3::prelude::{Bound, Py, PyAny, PyModule, PyModuleMethods, PyResult, Python}; use pyo3::sync::PyOnceLock; use pyo3::{pyclass, pymethods}; use sqlbuild_analysis::assembly::project::types::ObjectKey; @@ -22,6 +22,7 @@ use sqlbuild_analysis::graph::models::{ use crate::bindings::_helpers::boundary::panics::compiler_guard; use crate::bindings::_helpers::graph::views::lineage_graph_views; +use crate::bindings::_helpers::records::fields::field as native_field; /// `(key, deps, tags, model folder)`. pub(crate) type ResourceRow = (ObjectKey, Vec, Vec, Option); @@ -103,7 +104,7 @@ impl CompiledProjectGraph { } fn view(&self, py: Python<'_>, name: &str) -> PyResult> { - Ok(self.lineage_views(py)?.bind(py).getattr(name)?.unbind()) + Ok(native_field(self.lineage_views(py)?.bind(py), name)?.unbind()) } } 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 8566be13dd..d8794c5432 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 @@ -36,7 +36,7 @@ struct Vocabulary<'py> { } fn attribute_value<'py>(value: &Bound<'py, PyAny>, name: &str) -> PyResult> { - value.getattr(name) + native_field(value, name) } fn optional_value<'py>(value: &Bound<'py, PyAny>) -> PyResult> { @@ -160,7 +160,7 @@ fn python_node<'py>( let py: Python<'py> = discovered.py(); let get = |name: &str| attribute_value(discovered, name); let (metadata_field, metadata) = kind_metadata(py, kind, discovered)?; - let kind_member: Bound<'py, PyAny> = vocabulary.kinds.getattr(kind)?; + let kind_member: Bound<'py, PyAny> = native_field(&vocabulary.kinds, kind)?; let usages: Bound<'py, PyAny> = provider_usages(&get("function")?, &vocabulary.providers)?; let mut identity: Bound<'py, PyAny> = get("identity")?; if identity.is_none() { @@ -262,7 +262,7 @@ pub(crate) fn python_node_graph<'py>( }; let mut nodes: Vec> = Vec::new(); for (collection, kind, documented) in KINDS { - for discovered in discovered_inputs.getattr(collection)?.try_iter()? { + for discovered in native_field(discovered_inputs, collection)?.try_iter()? { nodes.push(python_node(&vocabulary, (kind, documented), &discovered?)?); } } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/hand_built/compiled_model.rs b/crates/sqlbuild-python/src/bindings/_helpers/hand_built/compiled_model.rs index f56efeff4c..c0f838f395 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/hand_built/compiled_model.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/hand_built/compiled_model.rs @@ -223,7 +223,7 @@ fn located<'py>( "code", "message", "line", "column", "start", "end", "severity", ] .into_iter() - .map(|name| item.getattr(name)) + .map(|name| native_field(&item, name)) .collect::>()?; rows.push(PyTuple::new(py, fields)?.into_any()); } @@ -248,7 +248,7 @@ fn located<'py>( let fields: Vec> = ["source_start", "source_end", "output_start", "output_end"] .into_iter() - .map(|name| span.getattr(name)) + .map(|name| native_field(&span, name)) .collect::>()?; rows.push(PyTuple::new(py, fields)?.into_any()); } 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 12feaa347e..2955fc5c7b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_facts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_facts.rs @@ -13,6 +13,7 @@ use crate::bindings::_helpers::janitor::janitor_keys::{ Folded, Key, SchemaKey, dedupe_schema_keys, folded_schema, integer, optional, record, sorted_schemas, text, tuple, }; +use crate::bindings::_helpers::records::fields::replaced; const QUERY_ARTIFACT_LIFECYCLE: ExecutorHost = ExecutorHost::QueryDiffArtifactLifecycle; const RELATION_LOOKUP: AdapterHost = AdapterHost::BuildRelationLookup; @@ -45,7 +46,7 @@ impl<'py> Warehouse<'py> { let mut arguments: Vec<(&str, Bound<'py, PyAny>)> = vec![("connection", self.connection.clone())]; arguments.extend(pairs.iter().cloned()); - call_with(&self.adapter.getattr(method)?, &arguments) + call_with(&native_field(&self.adapter, method)?, &arguments) } pub(crate) fn age_metadata_support(&self) -> PyResult { @@ -91,7 +92,7 @@ fn destination_keys<'py>( field: &str, ) -> PyResult)>> { let mut keys: Vec<(Key, Bound<'py, PyAny>)> = Vec::new(); - for node in project.getattr(field)?.try_iter()? { + for node in native_field(project, field)?.try_iter()? { let node: Bound<'py, PyAny> = node?; keys.push((Key::of(&native_field(&node, "destination")?)?, node)); } @@ -163,7 +164,6 @@ fn list_target_schema_relations<'py>( target_schemas: &[SchemaKey], ) -> PyResult> { let py: Python<'py> = warehouse.py(); - let replace: Bound<'py, PyAny> = imported(py, "dataclasses", "replace")?; let mut databases: Vec> = Vec::new(); for (database, _) in sorted_schemas(target_schemas) { if !databases.contains(&database) { @@ -203,15 +203,12 @@ fn list_target_schema_relations<'py>( else { continue; }; - let respelled: Bound<'py, PyAny> = replace.call( - (relation,), - Some(&keywords( - py, - &[ - ("database", optional(py, target.0.as_deref())?), - ("schema", optional(py, target.1.as_deref())?), - ], - )?), + let respelled: Bound<'py, PyAny> = replaced( + &relation, + &[ + ("database", optional(py, target.0.as_deref())?), + ("schema", optional(py, target.1.as_deref())?), + ], )?; match result.iter_mut().find(|(listed, _)| listed == target) { Some((_, relations)) => relations.push(respelled), @@ -436,7 +433,7 @@ pub(crate) fn direct_state_prune_candidates<'py>( continue; } let prune_sql: Bound<'py, PyAny> = call_with( - &warehouse.adapter.getattr(render)?, + &native_field(&warehouse.adapter, render)?, &[ ("database", optional(py, database.as_deref())?), ("schema", text(py, schema)), 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 31d2ba4bfd..0f582cee79 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_keys.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_keys.rs @@ -179,7 +179,7 @@ pub(crate) fn relation_age_timestamp<'py>( ) -> PyResult>> { let mut latest: Option> = None; for field in ["created_at", "last_altered_at"] { - let value: Bound<'py, PyAny> = relation.getattr(field)?; + let value: Bound<'py, PyAny> = native_field(relation, field)?; if value.is_none() { continue; } 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 a16c115d78..021334fc12 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_phases.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_phases.rs @@ -191,7 +191,7 @@ fn candidate_count(plan: &Bound<'_, PyAny>) -> PyResult { .getattr("drops")? .len()?; for field in CANDIDATE_FIELDS { - count += plan.getattr(field)?.len()?; + count += native_field(plan, field)?.len()?; } Ok(count) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_plan.rs b/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_plan.rs index 7735ab251f..5120fa24c3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_plan.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/janitor/janitor_plan.rs @@ -181,7 +181,7 @@ pub(crate) fn janitor_plan_text(plan: &Bound<'_, PyAny>) -> PyResult) -> PyResult { - let count = |name: &str| -> PyResult { result.getattr(name)?.len() }; + let count = |name: &str| -> PyResult { native_field(result, name)?.len() }; Ok(JanitorResultCounts { archived: count("archived")?, deleted: count("deleted")?, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/lifecycle_events/envelopes.rs b/crates/sqlbuild-python/src/bindings/_helpers/lifecycle_events/envelopes.rs index 1666b67c37..be7984ec7f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/lifecycle_events/envelopes.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/lifecycle_events/envelopes.rs @@ -87,7 +87,7 @@ fn text(value: &Bound<'_, PyAny>) -> PyResult> { /// Freeze a JSON object field of a frozen dataclass in place. fn freeze_object_field(owner: &Bound<'_, PyAny>, field: &str, path: &str) -> PyResult<()> { let py: Python<'_> = owner.py(); - let frozen: Bound<'_, PyAny> = frozen_json(&owner.getattr(field)?, path)?; + let frozen: Bound<'_, PyAny> = frozen_json(&native_field(owner, field)?, path)?; if !frozen.is_instance(&imported(py, "collections.abc", "Mapping")?)? { return Err(validation_error( py, @@ -106,7 +106,7 @@ fn freeze_object_field(owner: &Bound<'_, PyAny>, field: &str, path: &str) -> PyR fn lifecycle_event_check(event: &Bound<'_, PyAny>) -> PyResult<()> { let py: Python<'_> = event.py(); for field in EVENT_REQUIRED_TEXT { - required_text_check(&event.getattr(field)?, field)?; + required_text_check(&native_field(event, field)?, field)?; } let schema_version: Bound<'_, PyAny> = native_field(event, "schema_version")?; schema_version_check(&schema_version)?; @@ -126,7 +126,7 @@ fn lifecycle_event_check(event: &Bound<'_, PyAny>) -> PyResult<()> { } timestamp_check(&native_field(event, "occurred_at")?)?; for field in EVENT_OPTIONAL_TEXT { - optional_text_check(&event.getattr(field)?, field)?; + optional_text_check(&native_field(event, field)?, field)?; } freeze_object_field(event, "external_context", "external_context")?; freeze_object_field(event, "payload", "payload")?; @@ -154,7 +154,7 @@ fn diagnostic_log_check(record: &Bound<'_, PyAny>) -> PyResult<()> { return Err(validation_error(py, &problem)); } for field in DIAGNOSTIC_REQUIRED_TEXT { - required_text_check(&record.getattr(field)?, field)?; + required_text_check(&native_field(record, field)?, field)?; } let severity: String = native_field(record, "severity")?.extract()?; if let Some(problem) = diagnostic_severity_problem(&severity) { @@ -162,7 +162,7 @@ fn diagnostic_log_check(record: &Bound<'_, PyAny>) -> PyResult<()> { } timestamp_check(&native_field(record, "occurred_at")?)?; for field in DIAGNOSTIC_OPTIONAL_TEXT { - optional_text_check(&record.getattr(field)?, field)?; + optional_text_check(&native_field(record, field)?, field)?; } freeze_object_field(record, "fields", "fields") } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/lifecycle_events/validation.rs b/crates/sqlbuild-python/src/bindings/_helpers/lifecycle_events/validation.rs index cbde594342..53f9c1f9ad 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/lifecycle_events/validation.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/lifecycle_events/validation.rs @@ -95,7 +95,7 @@ pub(crate) fn timestamp_check(value: &Bound<'_, PyAny>) -> PyResult<()> { pub(crate) fn known_event_check(event: &Bound<'_, PyAny>) -> PyResult<()> { let mut correlations: Vec<(&'static str, bool)> = Vec::new(); for field in CORRELATION_FIELDS { - correlations.push((field, !event.getattr(field)?.is_none())); + correlations.push((field, !native_field(event, field)?.is_none())); } let facts = KnownEventFacts { schema_version: native_field(event, "schema_version")?.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 b5f9a5b675..c19ad7fbe2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_command.rs @@ -458,7 +458,7 @@ fn selector_views<'py>( fn view_options<'py>(views: &Bound<'py, PyAny>, names: &[&str]) -> PyResult> { let options: Bound<'py, PyDict> = PyDict::new(views.py()); for name in names { - options.set_item(*name, views.getattr(*name)?)?; + options.set_item(*name, native_field(views, name)?)?; } Ok(options) } 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 c427f67f25..7806212141 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_index.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_index.rs @@ -75,7 +75,7 @@ fn optional_text(value: Bound<'_, PyAny>) -> PyResult> { /// A source entry's `database.schema.table`, as far as it is set. fn source_relation_name(entry: &Bound<'_, PyAny>) -> PyResult> { let part = |name: &str| -> PyResult> { - match entry.getattr(name) { + match native_field(entry, name) { Ok(value) if !value.is_none() => Ok(Some(value.extract()?)), _ => Ok(None), } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/lineage/project_schema.rs b/crates/sqlbuild-python/src/bindings/_helpers/lineage/project_schema.rs index 623b015e46..9cb3beeafd 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/lineage/project_schema.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/lineage/project_schema.rs @@ -71,10 +71,10 @@ fn project_columns(project: &Bound<'_, PyAny>) -> PyResult> ("sources", "source_entry", LineageResourceType::Source), ("seeds", "schema_entry", LineageResourceType::Seed), ] { - for resource in project.getattr(attribute)?.try_iter()? { + for resource in native_field(project, attribute)?.try_iter()? { let resource: Bound<'_, PyAny> = resource?; let mut typed: TypedColumns = Vec::new(); - for column in native_field(&resource.getattr(entry)?, "columns")?.try_iter()? { + for column in native_field(&native_field(&resource, entry)?, "columns")?.try_iter()? { let column: Bound<'_, PyAny> = column?; typed.push(( native_field(&column, "name")?.extract()?, 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 cd6d231201..fd8825e1c3 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/load/ingestr_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/load/ingestr_command.rs @@ -98,7 +98,7 @@ fn resolved_source( let py: Python<'_> = config.py(); let value: Bound<'_, PyDict> = PyDict::new(py); for field in TEMPLATE_FIELDS { - let raw: Bound<'_, PyAny> = config.getattr(field)?; + let raw: Bound<'_, PyAny> = native_field(config, field)?; if LIST_FIELDS.contains(&field) { value.set_item( field, 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 1f623b4a63..52dd1441a9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/load/load_selection.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/load/load_selection.rs @@ -13,13 +13,14 @@ use sqlbuild_core::load_command::main::select_load_entries::select_load_entries use sqlbuild_core::load_command::models::{LoadEntry, LoadGraph}; use crate::bindings::_helpers::compile_attachment::effective_config::build_effective_connection_config; -use crate::bindings::_helpers::compile_driver::python_calls::{KeywordArguments, imported}; +use crate::bindings::_helpers::compile_driver::python_calls::KeywordArguments; use crate::bindings::_helpers::contracts::spec_resolution::loader_destination_parts; use crate::bindings::_helpers::contracts::spec_resolution::resolve_effective_adapter_name; use crate::bindings::_helpers::native_host::adapter::AdapterHost; use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::records::construct::new_record_with; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced_keywords; use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; use crate::bindings::_helpers::records::registry; @@ -47,22 +48,21 @@ pub(crate) fn environment_sources<'py>( target_config: &Bound<'py, PyAny>, ) -> PyResult>> { let py: Python<'py> = discovered.py(); - let replace: Bound<'py, PyAny> = imported(py, "dataclasses", "replace")?; let mut sources: Vec> = Vec::new(); for source_file in native_field(discovered, "source_files")?.try_iter()? { for source in native_field(&source_file?, "source_entries")?.try_iter()? { let source: Bound<'py, PyAny> = source?; let changes: Bound<'py, PyDict> = PyDict::new(py); for field in ["database", "schema"] { - let value: Bound<'py, PyAny> = source.getattr(field)?; + let value: Bound<'py, PyAny> = native_field(&source, field)?; let effective: Bound<'py, PyAny> = if !value.is_none() || target_config.is_none() { value } else { - target_config.getattr(field)? + native_field(target_config, field)? }; changes.set_item(field, effective)?; } - sources.push(replace.call((source,), Some(&changes))?); + sources.push(replaced_keywords(&source, &changes)?); } } Ok(sources) @@ -239,7 +239,7 @@ fn first_truthy<'py>( ) -> PyResult> { let py: Python<'py> = target_config.py(); if !target_config.is_none() { - let value: Bound<'py, PyAny> = target_config.getattr(field)?; + let value: Bound<'py, PyAny> = native_field(target_config, field)?; if value.is_truthy()? { return Ok(value); } From 2490ad8ced40b7cf3fadd21ec943411776027767 Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Sat, 17 Oct 2026 04:43:58 +0100 Subject: [PATCH 26/28] refactor: keep rich column lineage on the extension module The rich-lineage panic e2e test replaces build_project_column_lineage in the CLI process to prove native panics fail the command; no native trigger exists, so the call stays a module lookup with positional arguments. --- .../bindings/_helpers/lineage/lineage_command.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) 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 c19ad7fbe2..2d25b5cc21 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/lineage/lineage_command.rs @@ -41,7 +41,6 @@ use crate::bindings::_helpers::lineage::lineage_cache::{ }; use crate::bindings::_helpers::lineage::lineage_index::{graph_index, index_views, lineage_keys}; use crate::bindings::_helpers::lineage::project_lineage::build_direct_semantic_uses; -use crate::bindings::_helpers::lineage::project_lineage::build_project_column_lineage; use crate::bindings::_helpers::planner_selection::selectors::resolve_project_selector_tokens; use crate::bindings::_helpers::planner_selection::selectors::resolve_project_selectors; use crate::bindings::_helpers::planner_selection::selectors::select_project_graph; @@ -52,7 +51,7 @@ use crate::bindings::_helpers::planner_selection::unit_tests::reject_unit_test_s use crate::bindings::_helpers::presentation_text::terminal::stdout_supports_color; use crate::bindings::_helpers::records::construct::record_class; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::ids::{ErrorId, RecordId}; +use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, RecordId}; use crate::bindings::_helpers::records::registry; const UNKNOWN_SELECTOR_ERROR_CODE: &str = "S007"; @@ -374,13 +373,12 @@ fn column_lineage<'py>( py, (&message, rich && count >= RICH_STATUS_MODEL_THRESHOLD), || { - build_project_column_lineage( - &native_field(graph, "project")?, - None, + registry::native(py, NativeAttr::BuildProjectColumnLineage)?.call1(( + native_field(graph, "project")?, + py.None(), mode, - Some(PyFrozenSet::new(py, &candidates.model_names)?.as_any()), - ) - .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py)) + PyFrozenSet::new(py, &candidates.model_names)?, + )) }, ) } From 2e754ef31cfac4eb2e51d38910df74b20411886c Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Sat, 17 Oct 2026 04:52:53 +0100 Subject: [PATCH 27/28] refactor: read remaining multi-line record chains through native fields in a-l bindings Convert chained attribute reads on own records (source loop contexts, schema columns, macro enforcement, settings, clone policy, record fields) to records::fields::field, and rebuild compiled-project sources with replaced_with instead of dataclasses.replace. --- .../src/bindings/_helpers/clone/clone_command.rs | 8 +++----- .../_helpers/compiled_project/model_loop_macros.rs | 5 +---- .../_helpers/compiled_project/model_loop_schema.rs | 4 +--- .../bindings/_helpers/compiled_project/records.rs | 5 +---- .../_helpers/compiled_project/source_loop.rs | 12 ++++-------- .../_helpers/compiled_project/source_views.rs | 11 +++-------- .../src/bindings/_helpers/discovery/guidance.rs | 6 +----- .../src/bindings/_helpers/format_command/command.rs | 9 +++++---- 8 files changed, 19 insertions(+), 41 deletions(-) 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 7efc895ee9..162fa825b8 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/clone/clone_command.rs @@ -117,15 +117,13 @@ fn target_names(discovered: &Bound<'_, PyAny>) -> PyResult> { fn clone_policy(discovered: &Bound<'_, PyAny>, (target, flag): (&str, &str)) -> PyResult { let py: Python<'_> = discovered.py(); - resolve_target_config( + let config = resolve_target_config( py, &native_field(discovered, "project_config")?, &native_field(discovered, "local_config")?, &text(py, target), - )? - .getattr("clone")? - .getattr(flag)? - .is_truthy() + )?; + native_field(&native_field(&config, "clone")?, flag)?.is_truthy() } /// Discover the project, resolve and validate the targets, and resolve the adapter. diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_macros.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_macros.rs index 3f3a0bb971..64a85b3c40 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_macros.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_macros.rs @@ -119,10 +119,7 @@ pub(crate) fn expanded_with<'py>( declarations.clone().unbind(), consumer.clone().unbind(), ), - classes - .macro_context - .getattr(ENFORCE_ATTRIBUTE)? - .is_truthy()?, + native_field(&classes.macro_context, ENFORCE_ATTRIBUTE)?.is_truthy()?, classes.expansion.clone_ref(py), )?; let expansion: Bound<'py, NativeMacroExpansion> = Bound::new(py, expansion)?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_schema.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_schema.rs index a8d6362566..0bcfd48851 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_schema.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/model_loop_schema.rs @@ -75,9 +75,7 @@ pub(crate) fn model_schema<'py>( /// The schema's column names, which validation reads as the declared columns. pub(crate) fn schema_column_names(schema: &ModelSchema<'_>) -> PyResult> { - schema - .declaration - .getattr(COLUMNS_FIELD)? + native_field(&schema.declaration, COLUMNS_FIELD)? .try_iter()? .map(|column| native_field(&column?, NAME_FIELD)?.extract()) .collect() 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 7a237f6c43..ebad43bfe1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/records.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/records.rs @@ -49,10 +49,7 @@ fn record<'py>(value: &Bound<'py, PyAny>, class: RecordId) -> PyResult = native_field(&field?, "name")?; - fields.set_item( - &name, - value.getattr(name.downcast::()?)?, - )?; + fields.set_item(&name, native_field(value, &name.extract::()?)?)?; } class.call((), Some(&fields)) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/source_loop.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/source_loop.rs index 1807699f43..62fa0f7cd4 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/source_loop.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/source_loop.rs @@ -135,10 +135,8 @@ pub(crate) fn scoped_declarations<'py>( ) -> PyResult> { let visibility = &context.classes.visibility; if visibility.projects { - let contexts: Bound<'py, NativeDeclarationContexts> = visibility - .resolver - .getattr(NATIVE_CONTEXTS_ATTRIBUTE)? - .downcast_into()?; + let contexts: Bound<'py, NativeDeclarationContexts> = + native_field(&visibility.resolver, NATIVE_CONTEXTS_ATTRIBUTE)?.downcast_into()?; let (resolved, _) = contexts .get() @@ -173,10 +171,8 @@ pub(crate) fn file_declarations<'py>( let fields: Bound<'py, PyDict> = PyDict::new(py); fields.set_item("consumer", identity)?; if visibility.projects { - let contexts: Bound<'py, NativeDeclarationContexts> = visibility - .resolver - .getattr(NATIVE_CONTEXTS_ATTRIBUTE)? - .downcast_into()?; + let contexts: Bound<'py, NativeDeclarationContexts> = + native_field(&visibility.resolver, NATIVE_CONTEXTS_ATTRIBUTE)?.downcast_into()?; let (resolved, _) = contexts .get() diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/source_views.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/source_views.rs index 62c118242b..9b6b535b6a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/source_views.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/source_views.rs @@ -13,10 +13,9 @@ use crate::bindings::_helpers::compiled_project::model_input_view::{ use crate::bindings::_helpers::compiled_project::project::NativeCompiledProject; use crate::bindings::_helpers::graph::project_graph::ResourceRow; use crate::bindings::_helpers::records::fields::field as native_field; +use crate::bindings::_helpers::records::fields::replaced_with; const SOURCE_RESOURCE: &str = "source"; -const DATACLASSES_MODULE: &str = "dataclasses"; -const REPLACE_FUNCTION: &str = "replace"; /// `(database, schema)` native project assembly resolved for a source, or `None`. pub(crate) type SourceNamespace = Option<(Option, Option)>; @@ -114,15 +113,11 @@ impl SourceRecord { /// `dataclasses.replace(value, **changes)`. pub(crate) fn replaced( - py: Python<'_>, + _py: Python<'_>, value: &Bound<'_, PyAny>, changes: &Bound<'_, PyDict>, ) -> PyResult> { - Ok(py - .import(DATACLASSES_MODULE)? - .getattr(REPLACE_FUNCTION)? - .call((value,), Some(changes))? - .unbind()) + Ok(replaced_with(value, changes)?.unbind()) } fn read_source<'py, T>( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/discovery/guidance.rs b/crates/sqlbuild-python/src/bindings/_helpers/discovery/guidance.rs index 4d95ed7b3e..5537bccfc0 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/discovery/guidance.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/discovery/guidance.rs @@ -29,11 +29,7 @@ pub(crate) fn sql_analysis_guidance( } else { native_field(discovered_inputs, "project_config")? }; - if owner - .getattr("settings")? - .getattr(SQL_ANALYSIS_KEY)? - .is_truthy()? - { + if native_field(&native_field(&owner, "settings")?, SQL_ANALYSIS_KEY)?.is_truthy()? { return Ok(None); } Ok(Some(analysis_off(local_override, purpose))) 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 8790e33711..9ad208bc0e 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/format_command/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/format_command/command.rs @@ -142,10 +142,11 @@ fn fixture_inputs<'py>(resolved: &Resolved<'py>) -> PyResult Date: Sat, 17 Oct 2026 04:57:05 +0100 Subject: [PATCH 28/28] refactor: remove registry lookups left unused by the direct-call sweep --- .../_helpers/compiled_project/source_views.rs | 4 ++-- .../src/bindings/_helpers/records/ids.rs | 7 ------- .../src/bindings/_helpers/records/registry.rs | 21 ------------------- 3 files changed, 2 insertions(+), 30 deletions(-) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/source_views.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/source_views.rs index 9b6b535b6a..d90df591db 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/source_views.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/source_views.rs @@ -13,7 +13,7 @@ use crate::bindings::_helpers::compiled_project::model_input_view::{ use crate::bindings::_helpers::compiled_project::project::NativeCompiledProject; use crate::bindings::_helpers::graph::project_graph::ResourceRow; use crate::bindings::_helpers::records::fields::field as native_field; -use crate::bindings::_helpers::records::fields::replaced_with; +use crate::bindings::_helpers::records::fields::replaced_keywords; const SOURCE_RESOURCE: &str = "source"; @@ -117,7 +117,7 @@ pub(crate) fn replaced( value: &Bound<'_, PyAny>, changes: &Bound<'_, PyDict>, ) -> PyResult> { - Ok(replaced_with(value, changes)?.unbind()) + Ok(replaced_keywords(value, changes)?.unbind()) } fn read_source<'py, T>( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/records/ids.rs b/crates/sqlbuild-python/src/bindings/_helpers/records/ids.rs index 208a4bcf4d..5a02286c8a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/records/ids.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/records/ids.rs @@ -1024,10 +1024,3 @@ impl RecordId { 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) - } -} diff --git a/crates/sqlbuild-python/src/bindings/_helpers/records/registry.rs b/crates/sqlbuild-python/src/bindings/_helpers/records/registry.rs index ff4df385eb..0d0d3c3c17 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/records/registry.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/records/registry.rs @@ -13,8 +13,6 @@ use crate::bindings::_helpers::records::ids::{ErrorId, NativeAttr, 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. @@ -98,16 +96,6 @@ pub(crate) fn verify_attributes(module: &Bound<'_, PyModule>) -> PyResult<()> { ))) } -/// 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( @@ -128,15 +116,6 @@ pub(crate) fn native(py: Python<'_>, id: NativeAttr) -> PyResult( - py: Python<'py>, - id: RecordId, - name: &str, -) -> PyResult> { - class(py, id)?.getattr(name) -} - /// 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(|| {